PostDetail.jsx
const { data, isLoading, isError, error } = useQuery('comments', () => fetchComments(post.id) );
👉🏽 (post.id
에 따라) 각각 다른 데이터(query)를 받아오지만 같은 query key(comments
)를 사용하고 있기 때문
👉🏽 이미 알려진(만들어진?)(known keys) 키의 쿼리 데이터는 특정한 트리거가 있어야 refetch된다.
트리거의 예
- component remount
- window refocus
- running refetch function
- automated refetch (지정된 간격으로 refetch 자동 실행)
- query invalidation after a mutation
PostDetail.jsx
const { data, isLoading, isError, error } = useQuery(['comments', post.id], () => fetchComments(post.id) );
post.id
가 업데이트되면) 새로운 쿼리를 만든다.👉🏽 쿼리 함수에 있는 값(데이터를 구별할 때 쓰이는 값. 여기서는 post.id)이 쿼리 키(배열)에 포함돼야 한다!
inactive
상태가 된다.Post.jsx
... const maxPostPage = 10; // 최대 10페이지로 임의로 설정해 줌 (json placeholder가 제공하는 api에 limit가 10으로 설정되어 있음..) // 페이지에 따라 받아오는 데이터가 다른 쿼리 함수 async function fetchPosts(pageNum) { // 매개변수로 pageNum const response = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${pageNum}` // pageNum에 따라 다른 api 요청 ); return response.json(); } export function Posts() { const [currentPage, setCurrentPage] = useState(1); // 현재 페이지 state 만들기. 첫 페이지를 1페이지로 설정 const [selectedPost, setSelectedPost] = useState(null); const { data, isError, error, isLoading } = useQuery( ['posts', currentPage], // currentPage를 넣어서 쿼리키를 의존성 배열처럼 만든다 () => fetchPosts(currentPage), // 인자를 가지는 쿼리함수 { staleTime: 2000, } );
Next Page
, Previous Page
버튼 클릭currentPage
state를 업데이트Posts.jsx
... <button disabled={currentPage <= 1} // 현재 페이지가 1 이하면 비활성화 onClick={() => { setCurrentPage((previousValue) => previousValue - 1); // 클릭하면 현재페이지 -1 }}>Previous page</button> <span>Page {currentPage}</span> <button disabled={currentPage >= maxPostPage} onClick={() => { setCurrentPage((previousValue) => previousValue + 1); // 클릭하면 현재페이지 +1 }}>Next page</button>
- 다음 페이지가 캐시에 없기 때문에
Next Page
버튼을 누를 때마다 로딩 인디케이터가 보이는 현상...
👉🏽 Pre-fetching으로 (데이터를 미리 가져와서 캐시에 넣어서) 이러한 현상을 없애줄 수 있다.
pre-fetch의 목적
- 일단 캐시된 데이터를 표시해 주면서
- 백그라운드에서 데이터의 업데이트 여부를 조용히 서버에서 확인하는 것
- 만약 데이터가 업데이트 됐을 경우 해당 데이터를 페이지에 보여줌.
QQQ) 그럼 두 번 통신해서 비효율적인 것 아닌지.
stale
상태가 됨 (설정할 수 있지만 stale
이 기본값) (inactive..)stale
상태의 데이터를 보여줌 (캐시가 만료되지 않았다는 가정 하에! 만약 사용자가 cacheTime보다 오래 페이지에 머물렀다면 캐시가 없기 때문에 다시 로딩 인디케이터가 나타남)Post.jsx
Next Page
를 prefetchingimport { useQuery, useQueryClient } from 'react-query'; // useQueryClient 불러오기 ... export function Posts() { ... const queryClient = useQueryClient(); // queryClient 사용 // currentPage가 바뀔 때마다 pre-fetching 하기 (useEffect와 의존성 배열 사용) useEffect(() => { if (currentPage < maxPostPage) { // Next Page 클릭에 대한 prefetching을 만들 것이므로 const nextPage = currentPage + 1; queryClient.prefetchQuery(['posts', nextPage], () => fetchPosts(nextPage) // 해당 포스트(다음 페이지)를 fetch ); } }, [currentPage, queryClient]);
참고 :
Previous Page
의 데이터를 캐시에 유지하기
keepPreviousData: true
: 쿼리 키가 변경되어서 새로운 데이터를 fetching 하는 동안에도 마지막으로 fetch 되었던 데이터 값을 유지한다. (이전 페이지로 이동했을 때 해당 데이터가 캐시에 있도록)
Post.jsx
export function Posts() { const { data, isError, error, isLoading } = useQuery( ['posts', currentPage], () => fetchPosts(currentPage), { staleTime: 2000, keepPreviousData: true, // Previous Page 데이터 유지하기 } );
isFetching
: 데이터 가져오는 중 (쿼리 함수 완료 전) (데이터 존재 여부 상관 X)isLoading
: isFetching + 캐시된 쿼리 데이터 없음 (데이터 새로 가져오는 중)isLoading
사용 (데이터 없어서 새로 가져올 때만 보여줄 용도로 사용되므로)🐥 (어떤 상태이든) 캐시가 있다는 것...
=> fetching 중일 때 보여줄 수 있는 데이터가 있다는 것 (fetching은 다시 해야 함)
참고 : 강의에서 사용하는 jsonplaceholder는 실제 서버 데이터를 변경할 수는 없음. mutation 요청 보내는 건 가능하지만...
- mutate 함수를 리턴 (..?) (객체의 속성 함수로...)
- query key가 필요 없음 (데이터를 저장하지 않으므로)
isLoading
은 있지만isFetching
은 없음 (캐시되는 항목이 없으므로)- retry(재시도) 기본값 없음. (자동 재시도를 적용하고 싶다면 설정은 가능)
useMutation과 useQuery의 차이점
- useQuery의 queryFn은 매개변수 가질 수 없지만
const { data, isLoading, isError, error } = useQuery( ['comments', post.id], () => fetchComments(post.id) // 쿼리함수 (매개변수 x) );
- useMutation의 mutationFn은 매개변수를 가질 수 있다.
const deleteMutation = useMutation((postId) => deletePost(postId)); // 변이함수 (매개변수 O)
post.id
)로 넣으면 <button onClick={() => deleteMutation.mutate(post.id)}> Delete </button>
postId
)로 전달된다const deleteMutation = useMutation((postId) => deletePost(postId));
PostDetail.jsx
- Delete 버튼 누르면 삭제
import { useQuery, useMutation } from 'react-query'; // 삭제 mutationFn async function deletePost(postId) { const response = await fetch( `https://jsonplaceholder.typicode.com/postId/${postId}`, { method: 'DELETE' } ); return response.json(); } const deleteMutation = useMutation((postId) => deletePost(postId)); return ( <> ... <button onClick={() => deleteMutation.mutate(post.id)}>Delete</button> // mutate의 인자로 삭제할 post id를 전달 {deleteMutation.isError && ( <p style={{ color: 'red' }}>Error deleting the post</p> )} {deleteMutation.isLoading && ( <p style={{ color: 'purple' }}>Deleting the post...</p> )} {deleteMutation.isSuccess && ( <p style={{ color: 'green' }}>Post has been deleted</p> )} ...
PostDetail.jsx
return ( <> <h3 style={{ color: 'blue' }}>{post.title}</h3> <button onClick={() => deleteMutation.mutate(post.id)}>Delete</button> {deleteMutation.isError && ( // 에러 발생 시 <p style={{ color: 'red' }}>Error deleting the post</p> )} {deleteMutation.isLoading && ( // 로딩 시 <p style={{ color: 'purple' }}>Deleting the post...</p> )} {deleteMutation.isSuccess && ( // 요청 성공 했을 시 <p style={{ color: 'green' }}>Post has been deleted</p> )} <button>Update title</button> <p>{post.body}</p> <h4>Comments</h4> {data.map((comment) => ( <li key={comment.id}> {comment.email}: {comment.body} </li> ))} </> );
useQuery: 서버에서 데이터를 가져오고 최신 상태인지 확인하는 훅
staleTime : 데이터가 사용 가능한 상태로 유지되는 시간 (특정 트리거에 의해 re-fetch 되어 시작됨)
cacheTime : 데이터가 비활성화 된 후 남아있는 시간
쿼리 키가 변경되면 useQuery hook은 쿼리를 다시 실행함 (re-fetch)
( => 데이터 함수(쿼리 함수?)가 바뀌면 쿼리 키도 바뀜(바뀌어야 함). 데이터가 바뀌면 다시 실행될 수 있도록)
Great sharing knowledge to update and learn to improve programming skills. More access to advanced programs. Explore scratch games to learn and build together to develop skills. Establish and create quality products and programs.