자주 하는 react-query 질문

Q&A 큐레이션

1. Optimistic update는 언제 사용하나요?

안녕하세요 주니어 개발자입니다. 회사에서 간단한 프로젝트를 하고 있는 중인데요. 기능 중 버튼 클릭을 통해 데이터를 저장하는 과정에서 optimistic update를 사용해 구현하면 어떻겠냐는 제안을 받았습니다. optimistic update는 주로 언제 사용하게되나요? 그리고 혹시 react-query를 사용해서 optimistic update를 구현해보신분 있으신가요?


답변

Optimistic update는 사용자에게 API 요청의 성공 실패와 상관없이 성공한 모습을 먼저 보여주는 것을 의미합니다. 그래서 해석하면 낙관적 업데이트 아닐까요? ㅎㅎ 그래서 Optimistic update는 송금이나 주식 체결과 같은 민감한 정보에는 사용하면 안된다고 생각합니다. 예를들어 주식이 체결된 줄 알았는데 나중에 보면 매수가 안되어있으면 큰일이니까요. 하지만 실패해도 사용자에게 크게 영향을 주지 않는 것이라면 Optimistic update방식으로 구현해도 괜찮을 것 같습니다. 인터넷의 예시를 좀 찾아보면 SNS의 좋아요 기능 들이 예시로 많이 나오더라고요. react-query를 이용해 구현하는것은 해당 공식문서를 참고하시면 될것 같아요. https://tanstack.com/query/v4/docs/reference/useMutation 구현할 때, 핵심은 1. onSuccess가 아니라 onMutate를 사용하는것! API의 성공여부에 관계없이 먼저 실행됩니다. 2. onMutate의 return value는 update되기 전의 상태입니다. 그리고 만약 API 요청에 실패했을 때에 onError의 context로 받아서 해당 쿼리를 rollback 할 수 있습니다. 마지막으로 Optimistic update 관련해서 좋은 글이 있어서 요것도 공유드려요~~ https://story.pxd.co.kr/1193

이 질문 바로 가기

2. 리액트 쿼리 refetch vs invalidate

안녕하세요! 리액트 쿼리를 통해 페이지 네이션을 구현했습니다. 특정 페이지에서 데이터가 변경됐을 때 해당 페이지의 데이터를 다시 가져와서 페이지의 ui를 변경해주어야 합니다. 이때 refetch 또는 invalidate를 사용할 수 있는데 결과는 같은 것 같더라구요. 결과는 같지만 refetch와 invalidate의 동작이나 성능 또는 어떤 방면에서 차이가 있는지 문득 궁금해졌습니다! 혹시 데이터를 새로 가져올 때 어떤 함수를 쓰면 좋을까요? 답변을 통해 좋은 인사이트를 얻고 싶습니다! 감사합니다.


답변

안녕하세요 react-query에서 데이터를 새로 가져올 때 refetch와 invalidation은 다르게 동작합니다. refetch는 항상 데이터를 가져옵니다. 반면 invalidation은 쿼리를 stale하게 만듭니다. 쿼리가 stale 상태로 변경된다고 바로 데이터를 다시 가져오지 않습니다. 대략 다음과 같은 조건이 있습니다. 1. query instance가 마운트 될 때 (observer 생성) 2. 네트워크 재연결 3. 윈도우 다시 포커싱 4. refetch Interval로 인한 refetch 또한 위의 refetch 조건은 option을 통해 비활성화 될 수 있습니다. 따라서 항상 데이터를 가져와야 하는 경우가 아니라면 쓰임에 따라 invalidation을 사용하는게 미세하게나마 성능에 좋을 수 있습니다. 작성자님이 말씀해주신 페이지네이션 상황에서 얘기해보면 - 페이징 된 리스트 값에서 특정 페이지만 불러오고싶을 때는 둘 다 사용해도 괜찮을 것 같습니다. (해당 페이지 데이터의 상태 '변경') - 페이징 된 리스트 값이 '삭제'되어 모든 페이지의 구조가 변경되야 할 때는 refetch보다는 invalidation을 하는게 나을 것 같습니다. refetch는 모든 페이지의 데이터를 불러오지만 (키를 기준으로 inactive 한 데이터 까지 불러옴) invalidation은 우선 query를 stale하게만 만들기 때문에 active한 페이지의 데이터를 가져옵니다.

이 질문 바로 가기

3. react-query useQuery vs useMutation (데이터 조회 시)

안녕하세요. react-query에서 useQuery와 useMutation을 쓰는 도중 의문이 생겨서 질문드립니다. 보통 Read 작업은 useQuery를 쓰고, Create/Update/Delete 작업은 useMutation을 쓰라고들 말씀하시는데요, 특정 페이지 렌더링 시 자동 fetch하지 않고, 사용자 액션 발생 시에만 fetch 요청을 하는 경우 useQuery가 반환하는 refetch 함수를 쓰거나 useMutation가 반환하는 mutate 함수를 사용하면 구현이 가능하더라구요. (제가 테스트한 코드는 gist에 있습니다.) 여기서 궁금한 건 Read 작업임에도 불구하고 useMutation으로도 구현이 가능하다면 굳이 위 상황에서 useQuery로 구현했을 때의 이점이 있을까요?


답변

안녕하세요! 위 케이스와 유사한 질문에 답한 기억이 있는데, 참고해보시면 좋을 것 같아요 - https://careerly.co.kr/qnas/1165 우선 useQuery와 useMutation의 일반적인 차이는 알고 계신 것 같아서 생략하겠습니다. 근본적으로 둘은 외부 요청을 래핑한 개념이라는 공통점이 있습니다. 하지만 메인테이너의 말을 빌리자면 둘의 차이는 선언적 vs 명령적 차이라고 합니다. (참고: https://tkdodo.eu/blog/mastering-mutations-in-react-query#differences-to-usequery) 즉, useQuery (선언적)를 쓴다면 react-query에서 제공하는 여러 기능들 (ex. refetchOnWindowFocus, enabled)이 "알아서" 돌아가지만, useMutation (명령적)의 경우는 이런 기능들이 "알아서" 돌아가지 않습니다. 이유는 useQuery의 경우 선언하는 시점에 observer가 생성되지만 useMutation은 mutate을 사용하기 전까지 observer가 생성되지 않습니다. 또한 useMutation의 observer는 mutate가 돌아갈때마다 이전 observer를 unsubscribe 합니다. 다시 질문에 답하자면, 질문자님의 코드에서 데이터를 요청하는 시점은 사용자가 버튼을 클릭했을 때입니다. 그러면 사실상 mutate이나 refetch나 API 요청을 한번 래핑한 함수라서 동일하게 작동할겁니다. 다만, useQuery의 경우 결과가 캐싱이되어서 다른 곳에서 사용해야 할 경우 동일한 키를 제공해주면 공유가 가능합니다. (결과뿐만 아니라 로딩 상태, 에러 상태 등도 같이 공유됩니다) 하지만 refetch/mutate로 가져온 데이터를 굳이 캐싱해서 사용할 이유가 없고 위에 성원님이 말씀하신 컨벤션 정도를 제외한다면, 질문자님의 질문처럼 useQuery를 사용하는 이점이 퇴색될 것 같습니다. 오히려 useQuery를 사용해서 "알아서" 돌아가는 기능들 때문에 의도치 않은 이슈가 발생할수도 있어보이네요. ex. 의도치 않은 refetchOnWindowFocus 같은게 돌아서 onSuccess가 또 호출되는 경우? 정도가 생각나네요. 이것에 대해서 찾아보다보니, 이미 깃허브 이슈에서 비슷한 논의를 한적이 있어서 이것도 링크 남길게요. 질문자와 TkDodo님의 답변을 참고하시면 좋을 것 같습니다 :) - https://github.com/TanStack/query/issues/2304#issuecomment-1011598184

외 2개 답변 보러 가기

4. react query에서 placeholderData와 initial data 차이

안녕하십니까~ 프로젝트 코드를 보는 중에 react-query의 useQuery 옵션 중에 "placeholderData"라는 값에 더미 데이터를 넣어주고 있습니다. 근데 이걸 initialData라는 옵션에 넣어도 동일하게 작동하길래 둘의 차이가 뭔지 궁금하더라구요 ㅎㅎ 혹시 아시는 분 계신가요?


답변

둘은 비슷해보이지만 용도와 내부 동작이 다릅니다. placeholderData는 실제 데이터에 대한 응답을 받기 전 까지 "임시" 데이터를 보여주기 위한 용도로 사용됩니다. 내가 요청한 API의 응답이 도착하는 순간 화면에 보여지는 데이터는 실제 데이터가 됩니다. initialData는 "초기" 데이터를 보여주기 위한 용도로 사용됩니다. 내가 요청한 API의 응답이 도착하더라도 아직 데이터가 'fresh'한 상태라면 캐시에 남아있는 initialData가 화면에 보여지게 됩니다. 내부적으로 placeholderData는 캐시에 데이터를 저장하지 않는 반면, initialData는 데이터를 캐시에 저장합니다. 질문자 분이 말씀하신 것처럼 실제로 테스트 해보면 둘의 동작이 같다고 느껴지는데, 기본적으로 staleTime이 `0`으로 설정되있기 때문입니다. initialData가 존재하고 staleTime이 `0`이면 React Query는 곧바로 refetch를 실행해서 새 데이터를 가져옵니다. staleTime을 `1000*60`(ms)으로 바꿔보면 화면에 initialData가 1분 동안은 계속 남아있는 것을 확인할 수 있습니다. 헷갈리시면 placeholderData의 staleTime을 첫번째는 `0`으로, 두번째는 `1000*60`으로 한 번 바꿔보세요. 같은 결과를 확인하실 수 있을겁니다. placeholderData는 캐시에 아무것도 남지 않기 때문에 staleTime에 영향을 받지 않습니다. 둘의 차이점에 대한 자세한 내용은 React Query 메인테이너분이 작성하신 https://tkdodo.eu/blog/placeholder-and-initial-data-in-react-query 글을 읽어보시는 걸 추천드립니다.

이 질문 바로 가기

5. 리액트에서 데이터베이스가 변경될 때마다 재렌더링할 수 있는 방법이 있을까요?

안녕하세요! 저는 현재 리액트 공부 중에 있는 취준생입니다. 작은 프로젝트로 to do 웹을 만들고 있는 중에 데이터베이스와의 연동 부분에서 궁금한 것이 생겨 질문 올립니다. 데이터베이스가 변경될 때마다 웹도 갱신될 수 있게 만들고 싶은데, 어떻게 해야 웹이 항상 데이터베이스와 같을 수 있을지 잘 모르겠습니다. 제가 생각한 몇 가지 방법은, 1. 처음에만 데이터베이스를 state에 저장하고, 이후에는 데이터베이스와 state를 별개로 취급하는 방법 2. 데이터베이스가 수정될 때마다 get으로 데이터베이스를 다시 불러와 state를 변경하는 방법 3. 데이터베이스를 처음에 불러오고, 이후 한꺼번에 저장하는 방법 세가지 입니다. 제가 아직 미숙해서 이 방법이 옳은지 아닌지 판단할 수 없어 질문 올립니다. 구체적으로 알려주시지 않고, 검색할 수 있는 키워드 정도로만 알려주셔도 너무 감사합니다! 읽어주셔서 감사합니다 :)


답변

안녕하세요! 질문의 맥락을 보니 데이터베이스가 바뀐다기보다 데이터베이스 속에 todo 데이터가 바뀌는 상황을 말씀하시는 것 같네요. 이 페이지를 예시로 2가지 방법에 대해 설명드리겠습니다. *** A 방식 (데이터베이스 조회 요청을 자주 보내기) 1. 이 페이지를 처음 로드하면 질문자님의 질문 내용이 보여집니다. (이는 데이터베이스로 조회 요청을 한 뒤 받은 데이터를 사용하는 것이죠) 2. 질문자님이 수정 기능을 통해 질문 내용을 수정합니다. (이때 수정되어야 할 값들을 데이터베이스로 보내주면, 데이터베이스는 해당 값으로 업데이트합니다) 3. 수정이 정상적으로 완료되면, 페이지의 질문 내용이 갱신됩니다. (데이터베이스로 다시 한번 조회 요청을 해서 최신화된 데이터를 받습니다) A 예시는, 데이터에 직접적인 작업 (생성, 수정, 삭제)이 수행되면 해당 데이터를 사용하던 곳에서 다시 데이터베이스로 데이터를 요청하게끔 작업을 해주셔야 합니다. *** B 방식 (첫 조회 요청 때 전달 받은 데이터를 객체에 저장하고 객체를 계속 갱신해서 사용하기) 1. 이 페이지를 처음 로드하면 질문자님의 질문 내용이 보여집니다. (이는 데이터베이스로 조회 요청을 한 뒤 받은 데이터를 사용하는 것이죠. 이 데이터를 객체로 저장하고 페이지에서는 객체를 바라봅니다) 2. 질문자님이 수정 기능을 통해 질문 내용을 수정합니다. (이때 수정되어야 할 값들을 데이터베이스로 보내주면, 데이터베이스는 해당 값으로 업데이트합니다. 동시에 이전에 가지고 있던 데이터 객체도 수정해줍니다) 3. 수정이 정상적으로 완료되면, 페이지의 질문 내용이 갱신됩니다. (데이터베이스로 요청을 보내지 않고 수정된 데이터 객체를 계속 사용합니다. 원한다면 네트워크 요청 결과와 상관없이 수정된 데이터 객체의 내용을 보여줄 수 있습니다) B 예시는, 데이터를 불러온 후 객체에 저장한 뒤 페이지는 해당 객체를 바라보며 생성, 수정, 삭제 시에도 해당 객체를 수정해주는 방식입니다. *** A 예시는 질문자님이 생각하신 2번 방법과 유사하고 B 예시는 1번 방식과 유사합니다. 데이터베이스 상태와 웹의 상태를 일치하는 것은 생각보다 어려운 일이며 리액트 커뮤니티에서는 여러가지 방법으로 접근하고 있어요. A 방식의 라이브러리로는 react-query (tanstack-query)가 있고 B 방식의 라이브러리로는 redux, recoil, zustand 등이 있습니다. (물론 B 방식도 요즘은 추가 기능들이 생겨서 A 방식처럼 사용할 수 있습니다) B 방식처럼 리액트 앱 내에서 데이터를 지속적으로 관리해주는 방법을 상태 관리라고 합니다. 예전에는 B 방식이 좀 더 대중적이었지만 비교적 최근에 "리액트 앱이 외부에서 받아오는 데이터까지 B 방식으로 관리해야되나?" 라는 의문점을 가진 개발자분들이 A 방식을 시도했고 생각보다 괜찮은 접근 방법인 것 같아서 요즘은 A 방식도 많이 사용합니다. 현재는 A와 B 둘 중 하나를 골라야한다의 느낌은 아니고 경우에 따라 혼합해서 사용하는 느낌이에요. 리액트를 입문할 때 벨로퍼트님의 글들이 도움이 된 기억이 있어서 링크 첨부합니다. 참고해보시면 좋을 것 같아요 :) - https://react.vlpt.us/integrate-api/

이 질문 바로 가기

6. React Query 데이터 재조회 방식

안녕하세요. 이번 프로젝트에서 React Query를 처음 써보게 되었는데요. 데이터를 조회해올 때, 페이지와 검색어가 바뀔 때마다 API Call을 하는 것이 목적입니다. 제가 아는 방식은 아래와 같이 2가지인데, 두 방식이 어떤 차이가 있고, 어떤 선택이 좋은 선택인지 분간이 잘 되지 않아서 질문드립니다. (폰으로 작성중이라 코드가 좀 가물가물해서 문법이 약간 틀렸을 수도 있습니다!) ---------------------------------------- 1. useQuery만 사용 useQuery(['someList', page, keyword], () => { const params = { page, keyword, }; fetchList(params); }, { onSuccess: () => { // do something } }); 2. useQuery, useMutation, useEffect 사용 useQuery('someList', () => { const params = { page, keyword, }; fetchList(params); }, { onSuccess: () => { // do something } }); const { mutate } = useMutation(fetchList, { onSuccess: () => { queryClient.invalidateQueries('someList'); } }); useEffect(() => { const params = { page, keyword, }; mutate(params); }, [page, keyword]); ---------------------------------------- 사실 제가 아는 useMutation의 용도(생성, 수정, 삭제)와 코드 사이즈 측면을 생각해서 실제론 1번 방식을 사용하고 있기는 한데, 동작상 두 방식의 정확한 차이를 모르겠어서 이렇게 질문드립니다. 혹시 두 방식 모두 일반적이지 않다면, 일반적인 방식도 함께 알려주시면 정말 감사드리겠습니다!


답변

안녕하세요! 위에 올리신 코드를 기반으로 gist를 만들어보았는데요. https://gist.github.com/json9512/8bc447d0c7206ca73daca5da79531e59 1번 방법이 좀 더 일반적인 방법인 것 같습니다. 아시다시피 useMutation은 CUD을 위한 훅이고 useMutation으로 GET 요청을 하는 것은 설계 용도를 벗어나는 사용법이 아닌가 싶습니다. 우선 1번과 2번의 차이는 1번의 경우 param이 바뀔때 서버로 요청을 1번만 보내게 됩니다. 2번의 경우는 2번 보냅니다. mutate를 할때 fetchList로 한번 보내고 이후 "someList" 키가 invalidate 되면서 useQuery가 한번 더 fetchList를 호출합니다. 이론적으로 useQuery는 여러 컴포넌트에서 호출해도 같은 값을 공유하지만, useMutation의 경우는 값을 공유하지 않는 것으로 알고 있습니다. 다만, mutation의 상태를 저장/관리하기 위해 MutationCache라는 곳에 mutation 자체와 관련 데이터를 따로 저장하는데요. 이걸 감안한다면 2번 방법은 메모리도 더 많이 쓰는 방법이 되는 것 같네요. 정리를 하자면: - 1번 방법이 일반적인 방법인 것 같습니다. query key에 page, keywords를 넣어서 관리해도 무방해요. - 2번 방법이 메모리 차원에서 더 비효율적입니다 - 2번 방법은 네트워크 요청을 2번 보냅니다 - 2번 방법은 훅이 설계된 의도대로 사용되지 않아서, 버그를 발생 시킬 수 있습니다 - (사견) 2번 방법은 코드 의도를 파악하기 쉽지 않은 것 같습니다 - (사견) page, keyword 조합마다 쿼리가 저장될텐데, 캐싱이 꼭 필요한건지/유용하게 사용할 수 있는지도 고민이 필요한거 같습니다 여담으로 useEffect는 사용할 때 꼭 필요한것인지 고민하면 좋습니다. 참고할만한 링크들 첨부합니다: - https://beta.reactjs.org/learn/you-might-not-need-an-effect - https://tkdodo.eu/blog/mastering-mutations-in-react-query - https://tanstack.com/query/v4/docs/reference/MutationCache - https://tanstack.com/query/v4/docs/reference/useMutation - https://tanstack.com/query/v4/docs/reference/useQuery

외 1개 답변 보러 가기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직 개발자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

키워드로 질문 모아보기

기술, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!