📘

【React】親Componentから子ComponentのuseEffect内にある関数を呼び出す

2025/01/20に公開

結論

Parent Component

  • 親のコードは普通に書く
import React, { useRef } from "react";
import Child from "./Child";

const Parent = () => {
  const childRef = useRef<{ runStep: () => void }>(null);

  const handleClick = () => {
    if (childRef.current) {
      childRef.current.runStep();
    }
  };

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleClick}>Step を実行</button>
    </div>
  );
};

export default Parent;

Child Component

  • 子のComponentを変更する
  • 関数専用のrefを用意し、そのrefを親から渡されたref内で呼び出すように変更する
import React, { forwardRef, useImperativeHandle, useRef, useEffect } from "react";

interface ChildProps {}

const Child = forwardRef((props: ChildProps, ref) => {
  const stepRef = useRef<() => void>();

  useImperativeHandle(ref, () => ({
    runStep: () => {
      if (stepRef.current) {
        stepRef.current();
      }
    },
  }));

  useEffect(() => {
    // 実行したい関数を定義
    const run = () => {
      console.log("helloworld");
    };

    // ref に関数を設定
    stepRef.current = run;
  }, []);

  return <div>Child コンポーネント</div>;
});

Child.displayName = "Child";

export default Child;

普通にuseEffect内の関数を呼び出そうとするとエラーになる

Unhandled Runtime Error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

このようなエラーが出るので、useEffect内で定義した関数は別のref(今回で言うstepRef)に登録し、そのrefを親から呼び出すrefで呼び出すと良い。

おまけ

  • refの型定義のやり方について解説

普通にforwardRef<refの型, propsの型>で良いらしく、親コンポーネントではrefをuseRef<refの型>にすれば補完も効くので便利。

parent


//child componentの型定義をimportする
import { contentRef } from "./content";

//省略

 //useRefにimportしたtypeを入れる
 const contentRef = useRef<contentRef>(null);

  const handleButtonClick = () => {
    if (contentRef.current) {
      contentRef.current.runGradientDescentStep();
    }
  };

child

//refの型情報を定義する
export interface contentRef {
  runGradientDescentStep: () => void;
}

// forwardRef<refの型, propsの型>
const Content = forwardRef<contentRef, ContentProps>(
  ({ W, H, onNext, onBack, setBar }: ContentProps, ref) => {

    const gradientDescentStepRef = useRef<() => void>();

    useImperativeHandle(ref, () => ({
      runGradientDescentStep: () => {
        if (gradientDescentStepRef.current) {
          gradientDescentStepRef.current();
        }
      },
    }));

参考記事

https://qiita.com/TMDM/items/a9e6e9358c3f813a00b3
https://zenn.dev/mutachii/articles/a87b3cd2f4b9d3

Discussion