🥦

FunctionalComponentでsetStateのHookを実現する。

2024/01/17に公開

コード

カスタムフック

import * as React from 'react';

type Callback<T> = (newState: T) => void;
export type ObservingDispatcher<T> = (newState: T, observer?: Callback<T>) => void;

const useObserverbleState = <T>(initialState: T): [T, ObservingDispatcher<T>] => {
  const [state, setState] = React.useState<T>(initialState);
  const callbackRef = React.useRef<Callback<T> | undefined>(undefined);

  const setObserverbleState = React.useCallback((newState: T, callback?: Callback<T>) => {
    callbackRef.current = callback;
    setState(newState);
  }, []);

  React.useEffect(() => {
    if (callbackRef.current) {
      callbackRef.current(state);
      callbackRef.current = undefined;
    }
  }, [state]);

  return [state, setObserverbleState];
};

export default useObserverbleState;

使い方

import useObserverbleState from '@/scripts/hooks/useObserverbleState';
import * as React from 'react';

export const Component: React.FC = () => {
  const [value, setValue] = useObserverbleState(0);
  return (
    <button
      onClick={() => {
        setValue(value + 1, newValue => {
          console.log(newValue);
        });
      }}
    >
      {value}
    </button>
  );
};

解説

検索するとuseEffectを使った例がよく出てきます。

import * as React from 'react';

export const Component: React.FC = () => {
  const [value, setValue] = React.useState(0);
  React.useEffect(() => console.log(value), [value]);
  return <button onClick={() => setValue(value + 1)}>{value}</button>;
};

これだとClassComponent時代のthis.setStateのHookと違ってコンポーネントがマウントした時にも実行されてしまいます。それでも問題ない時も多いとは思いますが、今回は実行したくなかったのでこのような実装をしました。

下記の回答からほとんどいただいたコードですが、TypeScriptが自分の設定と違うのか、Reactのバージョンのせいかわかりませんが、エラーが出たので書き換えました。

https://stackoverflow.com/questions/54954091/how-to-use-callback-with-usestate-hook-in-react

Discussion