React setState 비동기 문제 해결

쏘뽀끼·2024년 11월 26일
0

react

목록 보기
19/25

모달을 글의 내용을 수정해서 업로드하는 로직을 짜고 있었는데 보내지는 값이 자꾸 이전 값으로 한 박자씩 늦게 보내졌다.!!!
useEffect도 걸어주었는데 왜 안될까.. 엄청 고민을 했다.

 📎 useEffect가 있어도 setState가 비동기로 처리된 이유

React의 useEffectsms
1. 컴포넌트가 마운트 된 후
2. 의존성 배열에 포함된 값이 변경된 후

이렇게 두 가지의 경우에 실행된다.

👀 useEffect의 실행 타이밍

useEffect의 의존성 배열에 포함된 값을 넣어 업데이트 될 때마다 실행시키게 하더라도 상태 업데이트와 useEffect사이에는 시간차가 존재한다.

  1. setState가 호출되면 상태 업데이트가 스캐줄링 된다.
  2. React는 업데이트된 상태로 컴포넌트를 다시 랜더링 한다.
  3. 새 랜더링이 완료된 후에 useEffect가 실행된다.

결과적으로 데이터로 보내는 함수가 최신으로 업데이트되기 전에 실행되면 여전이 이전 상테를 참조하게 되는 것이다.





😡 왜 의도한 대로 동작하지 않았는가

useEffect 의 실행은 렌더링 이후에 이루어지므로, 데이터를 저장하는 함수와의 타이밍 문제가 발생한다.

타이밍 문제의 흐름
1. setState로 상태 업데이트 요청이 들어간다.
2. React가 컴포넌트를 다시 렌더링 하고 useEffect를 실행하려고 준비한다.
3. 하지만 데이터를 저장하는 함수useEffect보다 먼저 실행되어 이전 상태를 참조한다.
4.useEffect는 이후에 실행되지만, 이미 데이터를 저장하는 함수는 요청을 보내버린 것





🙉 왜 useEffect로 최신 상태를 기다릴 수 없는가?

useEffect는 렌더링 이후의 부수 효과 처리를 위해 설계되었기 때문에, 상태 변경과 밀접하게 연관된 로직(예:요청보내기)에는 적합하지 않아서

useEffect(()=>{
mutation.mutate({id, data:datas[id]});
},[datas]);

위 코드처럼 useEffect에서 mutationa.mutate를 실행한다고 가정하면,

  • useEffect는 모든 상태 변경 시 실행되므로, 불필요한 요청이 발생할 수 있다.
  • 데이터를 저장하는 함수에서 상태 업데이트와 요청이 분리되면서, 로직의 의도가 불명확해질 수 있다.




🧃 해결 방법

문제가 발생한 코드

....
const[state,setState] = useState<{[key:number]:string}>({});

const handleChange = (id:number,value:string)=>{
setState({
...state,//이전 상태를 기반으로 업데이트 
[id]:value,
});
};


const handleSave = ()=>{
//현재 상태에서 data를 참조 
saveMutation.mutate({
id,
data:state[id],//여기가 문제!!
});
}

 const saveMutation = useMutation({
 mutationFn:(newData:dataT) => postData(newData),
 });
 
 useEffect(() => {
    console.log("Updated state:", state);
  }, [state]);
  
  
return(
...
)

}

앞서 말한 타이밍의 차이로 인해 useEffect로 상태 변경 확인은 가능하지만, 타이밍 문제를 해결하진 못한다.
useEffectstate 변경 이후에 실행되기에 상태가 반영되지 않은 값을 참조한다.
그러므로 state[id]undefined로 전달되거나 이전 값이 전달되는 것이다.




문제 해결 코드

....
const[state,setState] = useState<{[key:number]:string}>({});
const dataRef = useRef<{[key:number]:string}>({});

const handleChange = (id:number,value:string)=>{
setSate((prev)=>
const updatedData = {... prev, [id]:value};
dataRef.current[id] =value;//최신 상태를 dataRef에 저장
return updatedData;
});
};


const handleSave = (id:number)=>{
//최신 상태를 안전하게 참조 
const lastData = dataRef.current[id];//항상 최신 값 참조 
saveMutation.mutate({id, data:lastData});
}

 const saveMutation = useMutation({
 mutationFn:(newData:dataT) => postData(newData),
 onSuccess:(_,variables)=>{
 setState((prevData)=> ({
 ...prevData,
 [variables.id]: variables.data,
 }));
 },
 });
  
return(
...
)

}

해결 방법
1. useRef를 사용하여 최산 상태를 관리

  • useRef는 React의 상태 관리와 달리 컴포넌트가 재렌더링되지 않아도 값을 유지한다.
  • memoRef.current를 통해 항상 최신 값을 참조할 수 있도록 설계되었다.
  1. dataRef.currentsetState를 동시에 업데이트
  • setState는 화면에 즉각적으로 변경 사항을 반영하기 위해 사용되었다.
  • 그러나 비동기 업데이트 특성 때문에, 최신 값을 즉시 사용하려면 useRef를 활용할 수 있다.
onSuccess:(_,variables)=>{
setState((prevData)=> ({
...prevData,
[variables.id]: variables.data,
}));

useRef로 주석의 순서처럼 최신 상태를 Ref에 저장하고 항상 최신 값을 참조하게 만든다.
그리고 서버에서 성공 응답을 받은 후 setState를 사용해 로컬 상태를 다시 동기화 한다.
이로써 UI와 서버 데이터 간의 일관성을 유지하기 위해 중요하다.

0개의 댓글