Unstated Nextで状態管理
React Hooks以降、複数コンポーネント間の状態管理にはContextとuseReducerを組み合わせるのが一般的になっているようです。以前にこのブログ内でも取り上げました。
上記はプレーンなReactだけで実現できます。手軽な反面、コードが直感的にならない印象を受けます。Contextだけならともかく、useReducerを使うとReduxライクな記述にするか、または独自の書き方をするか、組む人に左右される部分もあります。
今回取り上げるUnstated Nextは、Reactコンポーネント間で状態を管理するライブラリです。これを使えばdispatchなどの概念は頭からポイできます🗑
yarn add unstated-next
以降、A・Bというコンポーネント間でフォームの状態値を取り扱う例を考えます。
createContainer
まず必要なのは、状態を保管するContainerを作成することです。これはReduxにおけるstoreと似ていて、状態値や更新関数が保管されます。store.jsというファイルを作りましょう。
import { useState } from 'react';
import { createContainer } from 'unstated-next';
export const useStore = () => {
// 入力中の状態
const [input, setInput] = useState('');
// テキスト値
const [text, setText] = useState('sample');
// 入力中の状態値を変更するハンドラ関数
const handleInput = event => {
setInput(event.target.value);
};
// 確定時のハンドラ関数
const handleSubmit = event => {
event.preventDefault();
if (input.length === 0) return;
setText(input);
setInput('');
};
return {
input,
text,
handleInput,
handleSubmit,
};
};
export const StoreContainer = createContainer(useStore);
最後一行以外は単なるCustom Hookです。そしてcreateContainer
はReact ContextにおけるcreateContext
です。引数にCustom Hookを指定しており、これでContainerが作成できます。中には状態値とハンドラが格納されていて、外部で使えるのはreturn
内の値です。
内部は単なるCustom Hookのため、直感的なコードになります。
useContainer
コンポーネントで値を読み出してみましょう。A.jsというファイルを作成します。
import React from 'react';
import { StoreContainer } from '../store';
const A = () => {
const form = StoreContainer.useContainer();
return (
<>
<p>{form.text}</p>
<input
type="text"
value={form.input}
onChange={form.handleInput}
/>
<button type="button" onClick={form.handleSubmit}>
確定
</button>
</>
);
};
export default A;
先程のStoreContainer
をインポートし、useContainer
を呼び出しています。
const form = StoreContainer.useContainer();
form
という変数でContainerでreturn
した値にアクセスできます。例えばform.input
とすれば、入力状態値を扱う変数を取り扱えます。
Provider
最後にContainer名.Provider
で利用するコンポーネントをラップします。React ContextのProviderと似ていますが、value
を渡す必要はありません。
App.jsに以下を記述します。
import React from 'react';
import A from './components/A';
import B from './components/B';
import { StoreContainer } from './store';
const App = () => (
<>
<StoreContainer.Provider>
<A />
<B />
</StoreContainer.Provider>
</>
);
export default App;
Bコンポーネントは、Aコンポーネントと名前以外はまったく同じコンポーネントです。これを実行してみると、AとBで同じ状態を利用できているのが確認できます。
ネストしたコンポーネントや他の状態やハンドラを呼び出すことも可能です。
少し凝った例 - ECサイト
React Routerで読み込むコンポーネント内でも利用できます。以下、簡易なECサイトのカートを再現してみました。※バックエンドなどは省いています。
コードはSadboxに置いてあります。
この例では利用していませんが、React.lazy
で動的に分割コンポーネントを読み込み、その中でContainerの値を取得もできます。
おわりに
Unstated Nextを使うと、複数コンポーネントの状態管理が簡単になります。200バイトというミニマムサイズなのも👍気になれば、是非。