【React Hook】useStateで関数を保持させる方法

# 概要
React Hookで `useState`を利用する際、中身に関数を保持させる方法を記載したポストです。

なんかそういう処理を書かないといけないケースがあったんですよ。ただ一瞬ハマったので備忘録として書いておきます。

(他にいいやり方があるのかもしれないですけど、あまり調査に時間かけられなかったので付け焼き刃的な方法です。ご了承下さい)

# やりたいこと
親コンポーネントMainと子コンポーネントChildがあるとして、Child側でセットした関数をMain側で呼び出したい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163629/30d6327f-987c-0075-9dd9-1422ee4f26f6.png)

# 失敗したやり方
単純に渡した場合、うまくいきません。

```ts
type Func = () => void;

const Main: React.FC = () => {
    const [func, setFunc] = useState<Func | undefined>(undefined);

    const handleOnClick = useCallback(() => {
        console.log("クリックされた");
        if (func) {
            func();
        } else {
            console.log("設定されてないよ");
        }
        
    },[func]);

    return (
        <div>
            <Child onSetFunc={setFunc} />
            <button onClick={handleOnClick}>MainButton</button>
        </div>
    );
};

const Child: React.FC<{ onSetFunc: (Func) => void }> = ({ onSetFunc }) => {
    const handleSetFunc = useCallback(() => {
        onSetFunc(() => {
            console.log("呼び出されるよ");
        });
    },[onSetFunc]);

    return (
        <div>
            <button onClick={handleSetFunc} >ChildButton</button>
        </div>
    );
};
```

これを実行し、 ChildButton -> MainButtonを押した場合に

```
クリックされた
呼び出されるよ
```

と出てほしいのですが、うまくいきません。
実際には ChildButtonをクリックしたタイミングで `呼び出されるよ` というコンソールログが出力され、 MainButtonをクリックしたら

```
クリックされた
設定されてないよ
```
と表示されることになります。

# なぜそうなるのか

単純なことなんですが、 useStateのsetterって、関数でもセットできるんですよね。
useStateの定義を見ると以下のようになってます。

```ts
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
```

この `SetStateAction` を見たらわかるように、普通の値だけじゃなく `(変更前の状態) => 変更後の状態` という関数も受け付けてます。

言葉じゃ分かりづらいので具体例を乗せると、以下のような感じでも状態のセットができるわけです。

```ts
const [counter, setCounter] = useState<number>(0);

// 1が設定される
setCounter(1);

// 直前の状態に1を足した数が設定される
setCounter((prev) => {
    return prev + 1;
});
```

どちらもよく使いますよね。
で、上の例だと `handleSetFunc` 内で行ってる `onSetFunc` は、この関数での設定だと判断されてしまい、ChildButtonを押下した際に `handleSetFunc` 内で設定した関数がそのまま呼び出され、かつ中の関数が何も返していないため undefined が設定されて MainButton押下の際にも呼び出されないという自体になります。

# 解決方法
２つあります。
１つ目は、 onSetFunc を呼び出す際に関数を返してあげればいいです。

```ts
// Mainとかは同じなので省略

const Child: React.FC<{ onSetFunc: (Func) => void }> = ({ onSetFunc }) => {
    const handleSetFunc = useCallback(() => {
        onSetFunc(() => {
            // 関数を返却する
            return () => {
                console.log("呼び出されるよ");
            }
        });
    },[onSetFunc]);

    return (
        <div>
            <button onClick={handleSetFunc} >ChildButton</button>
        </div>
    );
};
```

もう一つの解決方法は、関数を設定できないのだから関数を設定するのではなくinterface か typeでもぶっこんどけばちゃんと動きます。

```ts
// 型を変更
type Func = { onCall: () => void };

const Main: React.FC = () => {
    const [func, setFunc] = useState<Func | undefined>(undefined);

    const handleOnClick = useCallback(() => {
        console.log("クリックされた");
        if (func) {
            func.onCall();
        } else {
            console.log("設定されてないよ");
        }
        
    },[func]);

    return (
        <div>
            <Child onSetFunc={setFunc} />
            <button onClick={handleOnClick}>MainButton</button>
        </div>
    );
};

const Child: React.FC<{ onSetFunc: (Func) => void }> = ({ onSetFunc }) => {
    const handleSetFunc = useCallback(() => {
        const handleCall = {
            onCall: () => {
                console.log("呼び出されるよ");
            }
        };
        onSetFunc(handleCall);
    },[onSetFunc]);

    return (
        <div>
            <button onClick={handleSetFunc} >ChildButton</button>
        </div>
    );
};
```