見出し画像

RecoilとImmerを一緒に使ったuseRecoilImmerStateフックの紹介

こんにちわ。nap5です。


RecoilとImmerを一緒に使ったuseRecoilImmerStateフックの紹介をしたいと思います。


コンポーネント間で同じ状態を参照できるワークアラウンドを残しつつ状態の更新をimmerにした感じになります。


ストア側

import { atom } from 'recoil'

type Counter = {
  count: number
}

const counterState = atom<Counter>({
  key: 'counter',
  default: {
    count: 0,
  },
})

export { counterState }


フック側

import { useCallback, useMemo } from 'react'

import { Draft, produce } from 'immer'
import { RecoilState, useRecoilValue, useSetRecoilState } from 'recoil'

type DraftFunction<T> = (draft: Draft<T>) => void

export const useRecoilImmerState = <T>(state: RecoilState<T>) => {
  // https://github.com/immerjs/use-immer
  // https://prateeksurana.me/blog/simplify-immutable-data-structures-in-usereducer-with-immer/#:~:text=example%20that%20demonstrates%20how%20it%20works%20with%20TypeScript%3A
  // https://stackoverflow.com/a/73711545/15972569
  const activeState = useRecoilValue(state)
  const setActiveState = useSetRecoilState(state)

  const setState = useCallback(
    (valOrUpdater: T | DraftFunction<T>) => {
      return setActiveState(
        typeof valOrUpdater === 'function'
          ? produce(valOrUpdater as DraftFunction<T>)
          : (valOrUpdater as T)
      )
    },
    [setActiveState]
  )

  return useMemo(() => {
    return {
      activeState,
      setState,
    }
  }, [activeState, setState])
}


コンポーネント側

import NiceButton from '@/features/counter/components/NiceButton'
import { counterState } from '@/features/counter/stores/counter'
import { useRecoilImmerState } from '@/libs/useRecoilImmerState'

const Counter = () => {
  const { activeState, setState } = useRecoilImmerState(counterState)
  return (
    <div className='flex justify-center items-center flex-col gap-6 shadow-bebop p-4 rounded-xl'>
      <p className='text-2xl font-bold font-inter'>
        Count: {activeState.count}
      </p>
      <NiceButton
        labelName='Reset'
        type='button'
        onClick={() => {
          setState((prevState) => {
            return {
              count: 0,
            }
          })
        }}
      />
      <NiceButton
        labelName='+'
        type='button'
        onClick={() => {
          setState((prevState) => {
            return {
              count: prevState.count + 1,
            }
          })
        }}
      />
      <NiceButton
        labelName='-'
        type='button'
        onClick={() => {
          setState((prevState) => {
            return {
              count: prevState.count - 1,
            }
          })
        }}
      />
    </div>
  )
}

export default Counter


デモコードです。異なるコンポーネントで同じ状態がサブスクできている感じになっています。 counterページがデモになります。



こちらのデモの方がハンディかもです。






簡単ですが、以上です。


いいなと思ったら応援しよう!