Github
Postsreactdata fetching with race conditions (advanced react)

data fetching with race conditions (advanced react)

A set of two-state buttons that can be toggled on or off

keywords


  • Promise race
  • race condition problem

contents


문제상황 - Race condition

위 페이지에서 왼쪽 버튼을 빠르게 변경하면 fetching을 계속하고 response를 응답이 받을 때마다 렌더링된다. 또한 Issue 1이 보다 늦게 응답이 온다면 issue2 페이지에서 issue1의 내용을 보게된다.

해결 방법

  1. force re-mounting

    1const App = () => { 2 const [page, setPage] = useState('issue'); 3 4 return ( 5 <> 6 {page === 'issue' && <Issue />} 7 {page === 'about' && <About />} 8 </> 9 ); 10};

    <Issue> , <Aboute> 컴포넌트 내부에서 fetching

    컴포넌트 re-mount로 이전 컴포넌트가 unmount되면, 이전에 요청하던 fetching의 Promise 콜백의 상태를 사용하는 코드도 동작하지 않는다.

    비슷한 멍청했던 경험

    1const { mutate: updateSettings } = useUpdateSettings({ 2 onSuccess: () => { 3 queryClient.invalidateQueries(displayQueryKeys.settings()); 4 updateLayout({ displayId, viewId, layout: changedWidgetListRef.current }); 5 rest.onClose(); // <- updateLayout 실행하고 닫아서 6 }, 7 onError: (error) => { 8 handleError(error); 9 warningToast(); 10 }, 11}); 12 13const { mutate: updateLayout } = useUpdateLayout({ 14 onSuccess: () => { 15 // <- 실행 안됨 16 queryClient.invalidateQueries(displayQueryKeys.viewMode({ displayId, viewId })); 17 }, 18 onError: (error) => { 19 handleError(error); 20 warningToast(); 21 }, 22});
  2. drop incorrect result

    ref에 id를 넣어서 url이랑 비교해서 같으면 업데이트하는 방식

    1const Page = ({ id }) => { 2 const ref = useRef(id); 3 4 useEffect(() => { 5 ref.current = url; 6 7 fetch(`/some-data-url/${id}`).then((result) => { 8 if (result.url === ref.current) { 9 result.json().then((r) => { 10 setData(r); 11 }); 12 } 13 }); 14 }, [url]); 15};
  3. drop all previous results

    위 방식 이상하다. useEffect의 cleanup 함수 이용 방식

    Unmout, re-rendering 전에 조건을 변경한다.

    1useEffect(() => { 2 // set this closure to "active" 3 let isActive = true; 4 5 fetch(`/some-data-url/${id}`) 6 .then((r) => r.json()) 7 .then((r) => { 8 // if the closure is active - update state 9 if (isActive) { 10 setData(r); 11 } 12 }); 13 14 return () => { 15 // set this closure to not active before next re-render 16 isActive = false; 17 }; 18}, [id]);
  4. cancel all previous requests

    fetch options에 signal이라고 필드가 있는데, 이것을 위와 같이 이용하는 예제

    /call

    1useEffect(() => { 2 const controller = new AbortController(); 3 4 fetch(url, { signal: controller.signal }) 5 .then((r) => r.json()) 6 .then((r) => { 7 setData(r); 8 }); 9 10 return () => { 11 controller.abort(); 12 }; 13}, [url]);

위 방식들 중에서는 컴포넌트를 나누고, 컴포넌트 내부에서 fetching하는게 가장 이상적이라고 생각.

suspense 사용할 때도 waterfall 때문에 컴포넌트를 잘분리하는 것이 중요하고, 내부에서 필요한 데이터를 fetching하는 것이 코드를 읽거나, 디버깅에 용이하다고 생각함