Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active April 15, 2024 06:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/694a07476ba8c078720e13f7eadc4b34 to your computer and use it in GitHub Desktop.
Save yano3nora/694a07476ba8c078720e13f7eadc4b34 to your computer and use it in GitHub Desktop.
[js: react-query] React Query - Asynchronous (Ajax) query & mutation hooks for React. #js #react

Overview

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 Getting Started

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>
  )
}

v5

ばかみたいに変わってる。勘弁して。

vs SWR

SWRとReact Queryどっち使おうか検討したメモ

どっちも似たようなツールだけど、Vercel デプロイの場合は素直に公式提供の SWR を使って、そうじゃないときは React Query で API へ柔軟に対応するのがいいのかな。

Getting Started

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>
  </>
}

Settings

Window Focus Refetching

Window Focus Refetching

  • 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 })

Usage

useQuery

useQuery

Array Keys

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 が走る
  () => { /* ... */ }
)

Refetch Interval (Polling)

Automatically refetching with React Query

const {isLoading, data} = useQuery(
  'vehicle',
  async () => await fetch('https://xxx'),
  {
    refetchInterval: 6000, // ms
    refetchIntervalInBackground: false,
  }
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment