React v19 새로운 훅들

1차 카테고리
React
생성 일시
2025/04/19 05:19
최종 편집 일시
2025/04/19 14:35
발행여부
published
1 more property
React v19가 나오면서 새로운 훅들을 내용 정리. 전체적으로 Concurrent 렌더링을 최대한 활용하고, 코드의 가독성을 높이는데 주력한 느낌..

1. useTransition

useTransition은 React가 중요하지 않은 UI 업데이트를 뒤로 미루고, 더 중요한 작업(예: 입력 반응 등)을 먼저 처리하도록 도와주는 훅.

왜 사용해야 할까?

사용자 입력(setInput)은 빠르게 반응해야 하고, 리스트 필터링은 좀 늦게 떠도 괜찮음.
startTransition() 안의 setFiltered()는 느려도 되는 작업 → 비동기 처리 즉, React 내부에서 우선순위를 낮춘 비동기적 스케줄링
input의 UI 업데이트는 즉시 실행하지만 filtered 동작은 뒤로 미룸. isPending으로 상태까지 관리 가능
input에 검색어를 입력하고, 대량 데이터를 필터링해서 보여주는 UI의 상황
import { useState, useTransition } from 'react'; function SearchComponent({ data }) { const [input, setInput] = useState(''); const [filtered, setFiltered] = useState(data); const [isPending, startTransition] = useTransition(); const handleChange = (e) => { const value = e.target.value; setInput(value); startTransition(() => { const filteredData = data.filter((item) => item.toLowerCase().includes(value.toLowerCase()) ); setFiltered(filteredData); }); }; return ( <> <input value={input} onChange={handleChange} /> {isPending && <p>로딩 중...</p>} <ul> {filtered.map((item) => ( <li key={item}>{item}</li> ))} </ul> </> ); }
JavaScript
복사

2. useDeferredValue

변화는 바로 일어나되, 실제로 UI 반영은 나중에 일어나게 하고 싶을 때 사용.

왜 사용해야 할까?

사용자는 input 타이핑할 때마다 즉시 화면에 반영되는 걸 원함. 하지만 필터된 리스트가 너무 커서 렌더링이 느림 → 그래서 렌더링 기준이 되는 값만 지연
useTranstion과 좀 헷길리는 부분이 있는데, defer하는 대상이 다르다. useTransition → setState가 defer(즉, 무거운 상태 업데이트가 있을때) useDeferredValue → 값 자체의 렌더링 반영을 defer(렌더링만 무거울 때)
즉, 입력값은 즉시 반영할게. 대신 이 입력값을 기반으로 렌더링은 나중에. Debounce처럼 동작하는 것처럼 보이지만, useTransition처럼 취소가 되진 않고, 최신 값만 반영한다. 따라서, 디바운스가 필요한 경우에는 디바운스를 사용하는게 좋아보임.
const deferredSearchTerm = useDeferredValue(searchTerm); useEffect(() => { fetchData(deferredSearchTerm); // 너무 자주 안 불러지게 함 }, [deferredSearchTerm]);
JavaScript
복사

3. useActionState

useActionState는 form 제출 + 상태 업데이트 + 로딩 처리를 한 번에 처리하는 훅 서버 액션(form action)과 폼 기반 요청 처리에 최적화 되어있다.
기존에는 form 데이터를 처리하려면:
1.
useState로 상태를 만들고
2.
onSubmit 핸들러 만들어서
3.
fetch하고
4.
isLoading 상태 따로 관리하고
5.
응답 처리도 따로…
하지만 useActionState에서는 한번에 가능하다.
const [state, formAction, isPending] = useActionState(actionFn, initialState);
JavaScript
복사
state
현재 액션의 상태 (예: 에러, 결과 등)
formAction
<form action={formAction}> 처럼 바로 쓸 수 있는 함수
isPending
해당 액션이 실행 중인지 여부 (로딩 UI 용도)

로그인 관련

// 로그인 액션 async function loginAction(prevState, formData) { const email = formData.get("email"); const password = formData.get("password"); const res = await fetch("/api/login", { method: "POST", body: formData, }); if (!res.ok) return { error: "로그인 실패" }; return { success: true }; } // 로그인 폼 export default function LoginForm() { const [state, formAction, isPending] = useActionState(loginAction, null); return ( <form action={formAction}> <input name="email" /> <input name="password" type="password" /> <button disabled={isPending}>로그인</button> {state?.error && <p style={{ color: "red" }}>{state.error}</p>} {state?.success && <p style={{ color: "green" }}>로그인 성공!</p>} </form> ); }
JavaScript
복사
Tanstack-Query와 굉장히 유사하다. 만약 Tanstack-Query를 쓰고있다면 굳이 쓸 필요가 없어보인다..?

4. useOptimistic

버튼을 눌렀을 때 딜레이 없이 UI가 바로 바뀌어야 사용자 경험을 높일 수 있다. 하지만 서버 응답이 느릴 수도 있으니까 “일단 바뀌었다고 가정하고 보여주는” UI가 필요 기존에는 임시 상태를 만들어서 해결했었다.
const [optimisticState, addOptimistic] = useOptimistic(actualState, updaterFn);
JavaScript
복사
actualState
서버의 진짜 응답 데이터 (또는 초기 상태)
updaterFn(prev, newInput)
낙관적으로 반영할 방법
optimisticState
실제 보여줄 UI용 데이터 (진짜 + 낙관적 예측 포함)
addOptimistic(input)
낙관적 업데이트 트리거 함수

댓글 추가 UI

const [comments, setComments] = useState([]); const [optimisticComments, addOptimisticComment] = useOptimistic(comments, (prev, newComment) => { return [...prev, { id: 'temp', text: newComment, pending: true }]; }); async function handleSubmit(formData) { const commentText = formData.get('comment'); addOptimisticComment(commentText); // 먼저 UI에 추가 const res = await fetch('/api/comment', { method: 'POST', body: formData, }); const saved = await res.json(); setComments(prev => [...prev, saved]); // 진짜 데이터 반영 } // 댓글 폼 <form action={handleSubmit}> <input name="comment" /> </form> <ul> {optimisticComments.map((c) => ( <li key={c.id}>{c.text}{c.pending && ' (전송 중...)'}</li> ))} </ul>
JavaScript
복사
낙관적 UI를 구현한 경험은 없지만, 구현한다고 생각을 해보니 useOptimitic이 굉장히 유용하게 쓰일거 같다.

5. useFormStatus

useFormStatus는 form이 현재 제출 중인지, 성공했는지, 실패했는지 form 내부의 컴포넌트 어디에서든 알 수 있게 해주는 훅.
useActionState와 좀 헷갈리지만, useActionState가 좀 더 범용성이 있다.
import { useFormStatus } from "react-dom"; // ✅ React 19 function SubmitButton() { const { pending, data, action, method } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? "제출 중..." : "제출"} </button> ); }
JavaScript
복사

 Reference