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ページがデモになります。
こちらのデモの方がハンディかもです。
簡単ですが、以上です。