React Query
tannerlinsley/react-query - github.com
Examples > Simple - codesandbox.io
- React で Rest API などへの Ajax をする際の Hook ライブラリ
- 参照系を Query 、更新系を Mutation という括りで定義している
fetch()
とかを使った async の.isLoading
など state 参照ができる- ↑ 同様に
onSuccess
みたいな callback を仕込むことも可能
v4 で名前も package 名も変わってる。そういうのやめてくれだるい
以下は next じゃない plain react での getting started 。
$ npm i @tanstack/react-query
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
// window focus の toggle 時に
// query 系が refetch するのを抑止するやつ
queries: { refetchOnWindowFocus: false },
},
})
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
ばかみたいに変わってる。勘弁して。
どっちも似たようなツールだけど、Vercel デプロイの場合は素直に公式提供の SWR を使って、そうじゃないときは React Query で API へ柔軟に対応するのがいいのかな。
Installation
Quick Start
Using Next.js
Next.js + Prisma + NextAuth.js + React Query で作るフルスタックアプリケーションの新時代
nextjs 想定かつ、Hydrate component 使って hydration 時の prefetch による query cache も一緒にやる例。
$ npm i react-query
import React from 'react'
import { QueryClient, QueryClientProvider, Hydrate } from 'react-query'
function MyApp({ Component, pageProps }) {
//
// この session (user client x server side node) の組み合わせ毎に
// 使い回す query client を state 保持することで prefetch 時の
// cache data が異なる client (user) 間で混合されないようにしてる
//
const [queryClient] = React.useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
export default MyApp
// pages/posts.jsx
import {
dehydrate,
QueryClient,
useQueryClient,
useQuery,
useMutation,
} from 'react-query'
export async function getStaticProps() {
const queryClient = new QueryClient()
// prefetch をする例
await queryClient.prefetchQuery('posts', getPosts)
// prefetch 使うなら _app.tsx で定義した dehydratedState へ
// 脱水 (cache clear) 時の state を渡しとくきまりっぽい
return {
props: {
// for <Hydrate state={pageProps.dehydratedState}> in _app.tsx
dehydratedState: dehydrate(queryClient),
},
}
}
export default function Posts() {
const queryClient = useQueryClient()
const [postForm, setPostForm] = React.useState({
title: '',
body: '',
})
// query 実体、api 側では多分 Todo[] みたいな data 返してる
const getPosts = () => {
return fetch('/api/posts')
.then(res => res.json())
})
// mutation 実体、api 側では body の validation => new Todo やら
const postNewPost = (e) => {
e.preventDefault() // prevent form submit event
return fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(postForm),
})
}
// useMutation
const { mutate } = useMutation(postNewPost, {
onSuccess: (data, variables, context) => {
console.log('api response:', data)
console.log('input variables:', variables)
console.log('onMutate return:', context)
// mutation のあとなど cache 更新することが自明なときは
// 以下のように prefetch を invalidate (無効化) して再取得
//
queryClient.invalidateQueries('posts')
},
})
// useQuery
//
// この query に関しては prefetchQuery で一度 cache してるので
// ページ遷移などで再度の render (query) が必要になった場合にも
// 「cache 再利用して表示 => 裏側で再 fetch して差分あれば更新」のように
// hydration 時の cache 利用 + dehydration による cache 更新を自動で
// やってくれるようになる、わあい
//
const { data: posts, isLoading } = useQuery('posts', getPosts)
//
// import { Todo } from '@prisma/client' して
// useQuery<Todo[]>('posts', getPosts) とか generics も使える
// こいつは prefetchQuery('posts') してないので別名 query 扱い
// prefetch しない場合は従来どおり client side で hydration 後に
// query が都度走る感じ、必ず prefetch しないといけないとかはない
//
const { data: otherData } = useQuery('posts-2', getPosts)
// isLoading 使って表示わけたり
if (isLoading) return <span>loading...</span>
// isLoading 開けにデータあるかチェックして表示わけたり
if (posts.length === 0) return <span>no posts</span>
return <>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<span>{post.body}</span>
</li>
))}
</ul>
<form onSubmit={mutate}>
<input
id='title'
type='text'
value={postForm.title}
onChange={(e) => {
setPostForm({ ...postForm, title: e.target.value })
}}
/>
<textarea
id='body'
value={postForm.body}
onChange={(e) => {
setPostForm({ ...postForm, body: e.target.value })
}}
/>
<button>SUBMIT</button>
</form>
</>
}
- default では window focus の toggle 時に自動で useQuery refetch が走る
- 邪魔なら
new QueryClient()
時に設定から無効化しておく
// グローバルに無効化
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
})
function App() {
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
// 部分的に無効化
useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })
Query Keys > Array Keys
react-query: Refetch Query only if the state variable is changed
- 何らか state 更新されたら refetch したい
- useQuery の query key を
['posts', selectedUserId]
みたいにできる- useState との組み合わせとかで使えそう
- useEffect で state 変わったら refetch とかせんでいい
const [selectedTag, setSelectedTag] = useState(null)
const { data: posts, isFetching } = useQuery(
['posts', selectedTag], // selectedTag ちゃんが切り替わるたびに query が走る
() => { /* ... */ }
)
const {isLoading, data} = useQuery(
'vehicle',
async () => await fetch('https://xxx'),
{
refetchInterval: 6000, // ms
refetchIntervalInBackground: false,
}
)