はじめに
React hooksのuseEffectで非同期処理を書くことができます。この時、コンポーネントのstateを変更する場合がしばしばありますが、場合によっては複数回レンダリングすることがあります。
複数回レンダリングする例
import React, { useEffect, useState } from 'react';
const MyComponent = () => {
const [greeting, setGreeting] = useState('Hello');
const [name, setName] = useState('World');
useEffect(() => {
setTimeout(() => {
setGreeting('Hi');
setName('React');
}, 1000);
}, []);
return (
<div>{greeting}{' '}{name}</div>
);
};
現状だと、setTimeoutのコールバックでsetGreeting, setNameでそれぞれレンダリングされます。これが問題ないケースもあるかと思いますが、改善したいケースもあるかもしれません。
解決策1
useReducerを使うことで解決できます。
import React, { useEffect, useReducer } from 'react';
const initialState = {
greeting: 'Hello',
name: 'World',
};
const reducer = (state, action) => ({
greeting: action.greeting,
name: action.name,
});
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
setTimeout(() => {
dispatch({
greeting: 'Hi',
name: 'React',
});
}, 1000);
}, []);
return (
<div>{greeting}{' '}{name}</div>
);
};
解決策2
unstalbed_batchedUpdates
を使うことでも解決できます。Reactの内部的にはイベントハンドラで使われているものです。
import React, {
useEffect,
useState,
unstable_batchedUpdates as batchedUpdates,
} from 'react';
const MyComponent = () => {
const [greeting, setGreeting] = useState('Hello');
const [name, setName] = useState('World');
useEffect(() => {
setTimeout(() => {
batchedUpdates(() => {
setGreeting('Hi');
setName('React');
});
}, 1000);
}, []);
return (
<div>{greeting}{' '}{name}</div>
);
};
APIとしてはunstableなのでお気をつけください。
おわりに
個人的にはできるだけuseStateを使ってシンプルに組み立てたいですが、場合によってはuseReducerを使う方が問題が起こりにくいため活用するのが良いとは思います。その場合は、カスタムフックにしてロジックを切り分けた方が良いかもしれません。