はじめに
React Hooksを使っていてハマったところをカスタムフックで解決したときの記録です。
TypeScriptで書いてます。
useEffectの中で非同期処理をawaitで実行する
useEffect
をラップしたカスタムフックを作ればいい。
useEffect
で初期データをawait
を使って取得したい場合などに使える。
import { useEffect, DependencyList } from "react";
export default function useEffectAsync(
effect: () => any,
deps?: DependencyList
) {
useEffect(() => {
effect();
}, deps);
}
使い方:
useEffectAsync(async () => {
// 初期マウント時に一度だけデータを同期で取得する
const items = await fetchSomeItems();
console.log(items);
}, []);
現在の state を非同期処理の中でアクセスする
useEffect
でコンポーネントマウント時にaddEventListener
し、そのイベント処理内でuseState
で定義したstateにアクセスしたい場合などに。
useRef
でuseState
のstateの参照を保持するようにして、イベント処理内ではその参照を利用すると現在のstateの値を使える。
以下のようにカスタムフック化すると便利。
import {MutableRefObject, Dispatch, SetStateAction, useState, useRef } from 'react'
const useRefState = <T>(
initialValue: T
): [T, MutableRefObject<T>, Dispatch<SetStateAction<T>>] => {
const [state, setState] = useState<T>(initialValue);
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state;
}, [state]);
return [state, stateRef, setState];
};
1秒ごとにカウントアップしながらアラートを表示する場合の例:
import { useEffect } from 'react'
const component = () => {
const [count, countRef, setCount] = useRefState(0)
useEffect(() => {
setInterval(() => {
alert('Value: ' + countRef.current)
setCount(countRef.current + 1)
// 以下のようにしてしまうと初期描画時のcount(=0)がずっと利用されてしまう
// alert('Value: ' + count)
// setCount(count + 1)
}, 1000)
}, [])
return <div>{count}</div>
}
このとき、useEffect
は第二引数に[]
を指定しており最初の一度しか呼ばれないため、setInterval
に指定するコールバック関数でcount
を直接指定すると初期描画時のcount
の値を利用しつづけることになってしまう。かといってuseEffect
で再描画のたびにリスナ登録・リスナ解除のようなことをするのはイケてない。
そういうときにuseRef
が使える。
参考:
https://blog.castiel.me/posts/2019-02-19-react-hooks-get-current-state-back-to-the-future/