React 19가 다가온다
React 19에서는 새로운 훅이 추가되는데, 이 훅은 data fetching, form 생산성 향상에 대한 내용이다.
이 두 주제는 프론트엔드에서는 굉장히 중요한 주제인데, React에서는 이 주제애 대한 생산성 향상을 위해 어떻게 접근했을지 알아보자.
use(Promise)
Suspense는 이제 리액트와 뗄 수 없는 기술 스택이 되어가고 있다.
React-Query에서 제공하는 useSuspenseQuery라는 훅이 있긴 했지만,
React에서 제공하는 Suspense를 관리 훅이 따로 없었다.
이름을 왜 'use'로 지었는 지는 모르겠지만, 훅은 심플하고 강력해 보인다.
import { use } from 'react';
async function fetchData() {
return await fetch(...)
}
function Component() {
const data = use(fetchData);
return <div>{data}</div>
}
import { Suspense } from 'react';
function Container() {
return (
<div>
<Suspense fallback={<Loading />}>
<Component />
</Suspense>
</div>
);
}
이렇게 Promise를 use 내에 인수로 넣고 밖에서 Suspense로 감싸면 완성이다.
정말 간결하고 코드만 보아도 바로 의도를 알 수 있는 좋은 문법인 것 같다.
(+ 최근에 번들 사이즈에 대한 많은 스트레스를 받아왔는데, 아마 이 문법이 도입된다면 비동기 관련한 많은 라이브러리들을 빼고 번들 사이즈를 줄일 수 있을 것 같다.)
use(Context)
function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}
조건문 안에서 호출할 수 있다는 점만 다르고 기존 useContext와 동일하다.
(이것도 왜 이름이 use를 쓰는 지는 모르겠음..)
Form Action
function Container() {
async function action(formData) {
try {
const id = String(formData.get('id'));
const password = String(formData.get('password'));
const res = await axios.post('/api/login', {
id,
password,
});
console.log(res);
} catch (err) {
console.error(err);
}
}
return (
<form action={action}>
<input type="text" name="id" />
<input type="text" name="password" />
</form>
);
}
이 주제는 코드로 먼저 보여주는 게 좋을 것 같아서, 코드 블럭을 먼저 가져왔다.
React 18에서는 위와 같은 코드를 작성하게 되면 Warning이 나오게 되는데 19부터는 위와 같은 코드 작성도 가능해졌다.
위와 방법으로 Form Action을 구현할 수도 있지만, 앞으로 소개할 방법으로 Form Action을 구현할 수도 있다.
useFormState
위 formAction을 도와주는 훅으로 이 훅을 통해 반환된 결과 값도 표시할 수 있다.
아까와 같은 코드지만, Action에 대한 결과값 또한 화면에 보여줄 수 있게 만든 코드이다.
import { useFormState } from "react-dom";
function Container() {
async function action(formData) {
try {
const id = String(formData.get('id'));
const password = String(formData.get('password'));
const res = await axios.post('/api/login', {
id,
password,
});
return 'Success';
} catch (err) {
return 'Fail';
}
}
const [message, formAction] = useFormState(action, null);
return (
<form action={formAction}>
<input type="text" name="id" />
<input type="text" name="password" />
{message}
</form>
);
}
useFormStatus
이 훅은 부모(form)의 status를 포함한 몇 가지 값들을 가져올 수 있는 훅으로 가져올 수 있는 값은 아래와 같다.
1. pending: pending 여부 (boolean)
2. data: form에서 사용하고 있는 값들
3. method: form이 어떤 HTTP 메서드를 사용하고 있는지
4. action: 부모(form)에 전달된 action
이 반환값들로 아래와 같은 코드를 작성해 볼 수 있다.
import { useFormState, useFormStatus } from 'react-dom';
function Container() {
async function action(formData) {
try {
const id = String(formData.get('id'));
const password = String(formData.get('password'));
const res = await axios.post('/api/login', {
id,
password,
});
return 'Success';
} catch (err) {
return 'Fail';
}
}
const [message, formAction] = useFormState(action, null);
return (
<form action={formAction}>
<input type="text" name="id" />
<input type="text" name="password" />
{message}
<SubmitButton />
</form>
);
}
function SubmitButton() {
const { pending, data } = useFormStatus();
return <button type="submit">{pending ? data?.get('id') : 'Submit'}</button>;
}
이 훅을 사용해 Form Action의 상태를 사용한 코드 작성도 가능해졌다.
React 19에서 제공하는 훅들을 form에 하나씩 붙이고 있는데, 점점 form이 고도화되고 있다는 걸 느낄 수 있다.
useOptimistic
이 훅도 form과 관련된 훅으로 submit 되는 동안 미리 낙관적인 데이터를 보여줄 수 있는 방법으로 아래와 같은 코드를 작성할 수 있다.
import { useOptimistic, useState, useState, useState } from 'react';
import { useFormState, useFormStatus } from 'react-dom';
function Container() {
const [comments, setComments] = useState([]);
const [optimisticComments, addOptimisticComments] = useOptimistic(comments, (state, item) => [...state, item]);
async function action(formData) {
try {
const comment = String(formData.get('comment'));
addOptimisticComments(comment);
const res = await axios.post('/api/comment', {
comment,
});
setComments((state) => [...state, res.data]);
return 'Success';
} catch (err) {
return 'Fail';
}
}
const [message, formAction] = useFormState(action, null);
return (
<div>
<div>
<CommentList comments={optimisticComments} />
</div>
<form action={formAction}>
<input type="text" name="comment" />
{message}
<SubmitButton />
</form>
</div>
);
}
function SubmitButton() {
const { pending, data } = useFormStatus();
return <button type="submit">{pending ? data?.get('comment') : 'Submit'}</button>;
}
댓글을 작성할 수 있고, 댓글들을 표현하는 일반적인 커뮤니티 페이지에서 위와 같은 방식으로 코드를 작성할 수 있을 것처럼 보인다.
이렇게 작성하면 사용자들은 로딩이 없는 것처럼 느낄 수 있게 되어 사용자 경험이 크게 향상될 것 같다.
Subscribe to my newsletter
Read articles from Yoonsung Baek directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by