🐡

Jotai + TanStackQueryのすすめ

2023/06/28に公開

はじめに

Reactの状態管理ライブラリはいろいろな選択肢があります。
最近はZustandがトレンドっぽいですが、僕はJotaiを使うことが多いです。
どちらもdeveloperは同一ですが、設計思想に大きな違いがあります。
ここでは設計思想には言及しませんが、人によって好みは分かれそうな気はします。
今回は、JotaiTanStackQueryインテグレーション機能が実用で使えるレベルになってきたので、紹介していこうと思います。

TanStackQueryって?

旧称はReactQueryで、Reactのデータフェッチライブラリの1つです。
他に代表的なライブラリでSWRなどがありますが、自分は機能の豊富さでTanStackQueryをよく使っています。

Jotaiの基礎知識

Jotaiatomと呼ばれる最小単位のオブジェクトに値を保持することで状態管理を実現しています。
atomに保存された値はuseAtomを使って呼び出すことで、その値を参照することができます。
下記のコード例は、atomの値を使用する(更新する)カスタムフックを実装した例です。

count.ts
const countAtom = atom(0);

export const useCount = () => {
  const [count, setCount] = useAtom(countAtom);
  
  return {
    count,
    setCount,
  };
}

このカスタムフックを呼び出すことによって、任意のコンポーネント内でatomに保持された値を共有することが可能です。
もちろんカスタムフックを作らず、直接atomの値を使いたい場所でuseAtomすることも可能ですが、個人的にatomはなるべくexportしないようにするため、カスタムフックを作成することが多いです。
他にもコアな機能はまだまだありますが、今回は割愛させていただいて本題に入りたいと思います。

TanStackQueryインテグレーション

インストール

Jotaiには他ライブラリとのインテグレーション機能が存在します。
今回紹介するのは、TanStackQueryとのインテグレーション機能です。
まず必要なパッケージをインストールします。

$ npm i jotai jotai-tanstack-query @tanstack/query-core

atomWithQuery

インストールが完了したら、atomWithQueryという関数が使用可能になっていると思います。
この関数は引数として、TanStackQueryuseQueryと同じものを渡すことができます。

api.ts
import { atomsWithQuery } from 'jotai-tanstack-query';
import { useAtomValue } from 'jotai';

const [getBooksAtom, getBooksQueryAtom] = atomsWithQuery({
  queryKey: ['books'],
  queryFn: async () => {
    return fetchBooks();
  },
});

export const useBooks = () => {
  return {
    books: useAtomValue(getBooksAtom),
  };
};

取得されたデータはgetBooksAtomに格納されます。
getBooksQueryAtomにはTanStackQueryuseQueryの返り値が格納されています。
単に取得データのみ扱いたい場合はgetBooksAtomisLoadingerrorなどを扱いたい場合はgetBooksQueryAtomを使用します。
また、getBooksAtomを使用する場合、データフェッチ中はPromisethrowするためSuspenseを併用する必要があります。
また、フェッチ関数に渡す引数を動的に変えたいときは以下のように書くことができます。

api.ts
import { atom, useAtomValue, useSetAtom } from 'jotai';
import { atomsWithQuery } from 'jotai-tanstack-query';

const bookIdAtom = atom(0);

const [getBookAtom, getBookQueryAtom] = atomsWithQuery((get) => {
  const bookId = get(bookIdAtom);

  return {
    queryKey: ['book', bookId],
    queryFn: fetchBook(bookId),
    enabled: !!bookId,
  };
});

export const useBooks = () => {
  return {
    book: useAtomValue(getBookAtom),
    setBookId: useSetAtom(bookIdAtom),
  };
};

bookIdAtomの値が変わると、その度にデータフェッチが走ってくれます。

atomsWithMutation

TanStackQueryではデータ取得用のuseQueryのほかにデータ更新用のuseMutationも存在します。
もちろんこのインテグレーション機能ではmutateも使用することが可能です。
mutateを使用するには、atomsWithMutationを使用します。

api.ts
import { atomsWithMutation } from 'jotai-tanstack-query';

const [createBookAtom, createBookMutationAton] = atomsWithMutation((get) => ({
  mutationKey: ['createBook'],
  mutationFn: async (variables) => {
    return createBook(variables);
  },
  onSuccess() {
    get(getBooksQueryAtom).refetch();
  },
}));

export const useCreateBook = () => {
  const createBookMutation = useAtomValue(createBookMutationAton);

  return {
    createBook: createBookMutation.mutate,
    isCreateBookLoading: createBookMutation.isLoading,
  };
};

mutateの結果がcreateBookAtomに、TanStackQueryuseMutationの返り値がcreateBookMutationAton格納されます。
atomsWithQueryatomsWithMutationともに、それぞれTanStackQueryuseQueryuseMutationのオプションや返り値をそのまま使用することができます。

queryClientAtom

インテグレーション機能で使用されるQueryClientも指定可能なので、TanStackQueryの便利機能であるdevtoolsQueryClientに実装されている関数も使用することができます。
1つのQueryClientインスタンスを共有するには以下のように書きます。

libs/tanstack-query.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient();
_app.ts
import { queryClientAtom } from 'jotai-tanstack-query';
import { queryClient } from '@/libs/tanstack-query';

export default function App() {
  useHydrateAtoms([[queryClientAtom, queryClient]]);
  
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools />
      // children
    </QueryClientProvider>
  );
}

さいごに

今回は、JotaiTanStackQueryインテグレーション機能について紹介しました。
採用するライブラリとして、JotaiTanStackQueryが選択肢に入るのであれば、一度使ってみてはいかがでしょうか!

ユニフォームネクスト株式会社

Discussion