🍀

React + unstated-nextを使って、非同期(async/await)で値を管理

2021/03/14に公開

React hooksとunstated-nextを活用することで、処理とコンポーネントを分離し、非同期であることを意識せず値をやり取りすることができます。

デモも公開しています。

また、unstated-nextのソースコードはたった40行で、useContextを扱いやすくするためのモジュールであるため、簡単にReact hooksのみのネイティブな実装に書き換えることができます。

フックをコンテナ化

props-container.ts
import { useState } from "react";
import { createContainer } from "unstated-next";

const hooks = (initialState: string = "") => {
  const [props, setProps] = useState(initialState);

  const updateProps = async (arg: string) => {
    // 処理中であることを通知
    setProps("processing...🐢");

    // 重たい処理
    const heavyProcess = new Promise<string>(resolve =>
      setTimeout(() => resolve(arg + " finished🎉"), 1000)
    );

    // 重たい処理の返り値
    const response = await heavyProcess;

    // 結果を出力
    setProps(response);
  };

  return { props, updateProps };
};

export default createContainer(hooks);

setPropsはコンテナ内で使用し、外部からはupdatePropsのみを使用します。updatePropsを外部から呼び出すことで、引数に指定した値が非同期処理終了後、propsに格納されます。

サンプルコードでは処理中であることを示す値をセットしていますが、非同期処理が完了するまで値を変更する必要がない場合は、updatePropsの最初のsetPropsは必要ありません。

Provider以下のコンポーネントで、コンテナを使用できるようにする

app.tsx
import React from "react";
import PropsContainer from "./props-container";

import Form from "./Form";

// Provider以下のコンポーネントであれば、useContainerで共通のフックを使用できます
const App: React.FC = () => (
  <PropsContainer.Provider initialState={"init"}>
    <Form />
  </PropsContainer.Provider>
);

export default App;

サンプルコードの子要素が1つだけなので余り恩恵はありませんが、Providerを定義することで子要素のコンポーネント全てで先ほどのコンテナを使用できます。

この辺りはuseContextと同様です。

コンテナを使用する

form.tsx
import React from "react";
import PropsContainer from "./props-container";

const Form: React.FC = () => {
  // コンテナ内のフックを呼び出す
  const { props, updateProps } = PropsContainer.useContainer();

  const [input, setInput] = React.useState("");

  // 値を扱う際に非同期であることを意識する必要はない
  const update = () => updateProps(input);

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) =>
    setInput(e.target.value);

  return (
    <div>
      <input type="text" value={input} onChange={onChangeInput} />
      <button onClick={update}>update</button>
      <h1>{props}</h1>
    </div>
  );
};

export default Form;

コンポーネントサイドでは、updatePropsの引数が非同期で設定されることを意識する必要はありません。
以上で、以下のような実装を実現できます。

以上が現在私が活用している方法です。まだまだ勉強不足なところもあり、どうすれば最適なのかしっかり把握できている自信はありません。
もっといい方法をご存知でしたらご教授ください🙇‍♂️

Discussion