⚛️React Recoilでカスタムフックを作ってRecoilを隠匿する

3 min読了の目安(約2900字TECH技術記事

課題

初めてrecoilを使うが、component側でrecoilを使っている事を意識したくない。

カスタムフックの作成

  1. component側でrecoilをimportしない
  2. カスタムフックでstateに関する関数を呼び出せるようにする

最小構成

定義側

hooks/userState
import React from 'react';
import { atom, useRecoilState } from 'recoil';
import { User } from '@/types';

const userState = atom<User>({
  key: 'userState',
  default: null,
});

export const useUser = () => {
  const [user, setUser] = useRecoilState(userState);

  return {
    state: user,
    set: setUser,
  };
};

component側

import React, { FC } from 'react';
import { useUser } from '@/hooks/userState';

const UserName: FC = () => {
  const { state } = useUser();
  
  return <p>{state.name ?? 'No Data'}</p>
}

計算結果を返すgetterの作成

定義側

hooks/userState
import { useCallback} from 'react';
import { atom, useRecoilState } from 'recoil';
import format from 'date-fns/format';
import { User } from '@/types';

const userState = atom<User>({
  key: 'userState',
  default: null,
});

export const useUser = () => {
  const [user, setUser] = useRecoilState(userState);

  // 誕生日を指定のフォーマットに変換した文字列を返却する
  const getStrBirthday = useCallback(
    (strFormat?: string = "YYYY年M月d日") => {
     return format(user.birthday, strFormat);
    },
    [user.birthday]
  );

  return {
    state: user,
    set: setUser,
    getStrBirthday
  };
};

component側

import React, { FC } from 'react';
import { useUser } from '@/hooks/userState';

const UserName: FC = () => {
  const { state, getStrBirthday } = useUser();
  
  return <div>
    <p>{state.name ?? 'No Data'}</p>
    <p>{getStrBirthday()}</p>
  </div>
}

Promiseを返すgetterを作成する

定義側

hooks/userState
import { useCallback} from 'react';
import { atom, useRecoilState } from 'recoil';
import { fetchArticlesByUserId } from '@/api'
import { User, Article } from '@/types';

const userState = atom<User>({
  key: 'userState',
  default: null,
});

export const useUser = () => {
  const [user, setUser] = useRecoilState(userState);
  
  // こんな関数が欲しいかどうかは置いといて、例として見てください。てへ
  const fetchMyArticles = useCallback(
    async () => {
     const options = { limit: 10 };
      const articles: Article[] = await fetchArticlesByUserId(
        user.id,
        options
      );
      return articles;
    },
    [user.id]
  );

  return {
    state: user,
    set: setUser,
    fetchMyArticles
  };
};

component側

import React, { FC, useState, useEffect } from 'react';
import { useUser } from '@/hooks/userState';
import { Article } from '@/types';

const initialArticles: Article[] = [];

const UserName: FC = () => {
  const { state, fetchMyArticles } = useUser();
  const [articles, setArticles] = useState(initialArticles)
  
  useEffect(() => {
    fetchMyArticles().then((data) => setArticles(data));
  }, [fetchMyArticles]);

  return <div>
    <p>{state.name ?? 'No Data'}</p>

    // 表示に関してはテキトーに書きました。
    {articles.map((article) => (
     <p>{article.body}</p> 
    ))}
  </div>
}