【React】親 props の変更が反映される useState のカスタムフックを作る


こんにちは、フロントエンドエンジニアの茶木です。
年内最後の記事となりそうです。
便利なのでよく使う React の hook があります。

状況解説

DBに干支(の絵文字)が収録されており getAPI により取得後表示します。表示内容は input から変更可能で、変更があれば putAPI にてDBを更新します。

interface Props: { eto: string, reload: () => void }
const TextEto = ({ eto }: Props): ReactElement | null => {
  const [value, setValue] = useState( eto );
  const handleChange = useCallback( async
    (e: ChangeEvent<HTMLTextAreaElement>) => {
      setValue(e.target.checked);
      await putEtoApi({eto: value});
      await reload();
    },
    [setValue]
  );
  <Input value={eto} />
}
const [eto, reload] = useGetApi(getApi);
if(!eto) return <>Loading...</>;
return <TextEto eto={eto} reload={reload} />

フローから説明します。

  • useGetApi(getApi) を通して DBから eto を取得して TextEto にデフォルト表示する。
  • ユーザーは TextEto を任意のテキストに変える
  • 変更内容は putEtoApi({ eto: value }) で送信されてDBを更新する。

ここまでのフローに追加で、putEtoApi({ eto: value }) をコールするとBE側でバリデーションが走ります。ふさわしくない干支で更新を試行した場合、DBの更新が却下されるとしましょう。

その場合、入力値をDBから再取得して欲しいので reload を呼び、コンポーネントの呼び出し側の propseto を変更します。

問題発生

・・・と、ここで問題になるのが、コンポーネントの呼び出し側の propseto を変更しても、useState でロックされているので、 コンポーネント側の value の更新は行われません。

const [value, setValue] = useState( eto );

これは、useState を使わずに propseto をそのまま input の value とすれば解決するのですが、 onChange の機構がなくなってしまいます。onChange の機構は親コンポーネント側に移譲することもできますが、表示と変更はコンポーネントの中だけで完結していて欲しいと思います。

欲しい物

欲しい物がはっきりしました。

useState の setValue の機能を持ちながらも、初期化に使った変数の変更も value に反映されるフック

カスタムフック化する

以下になります。題して useStateObserved です。

import { DependencyList, useEffect, useState } from "react";
export const useStateObserved = <T>(
  value: T,
  deps: DependencyList
): [T, (t: T) => void] => {
  const [state, setState] = useState<T>(value);
  useEffect(() => {
    setState(value);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);
  return [state, setState];
};

このカスタムフックの useStateObserved

const [value, setValue] = useState( eto );

useState と差し替えます。

const [value, setValue] = useStateObserved( eto, [eto] );

まず、setValue は setState hooks で生成されたものと同等なので、ユーザー入力による TextEto の再描画は変わらず行われます。

そして今回のポイントの第2引数の [eto] は dependency です。 [eto] に変更があった場合、つまり reload コール後の props 書き換えで eto に差異が生まれた場合、TextEto の再描画が行われます。差異がない場合、つまりバリデーションに問題がなく DB の更新が正しく行われた場合、ユーザー入力の内容とDBの内容が一致するため再描画は行われません。

おわりに

React は適切な描画が行われることで、ユーザーの体感が変わってきます。

来年も良い記事をお届けできるようにがんばりたいと思います。
どうぞ、来年も良いお年を。

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。