

React でウェブツールを作成しています。  
親コンポーネントが別のものに変わったとき、子コンポーネントの useEffect が発火しないようにすることは可能でしょうか？

最小のサンプルを用意いたしました。  
CodeSandbox: <https://codesandbox.io/s/jovial-kowalevski-lkdbx>

[code]

    function ComponentA(props) {
      return (
        <div style={{ backgroundColor: "pink" }}>
          <p>Component A</p>
          {props.children}
        </div>
      );
    }
    
    function ComponentB(props) {
      return (
        <div style={{ backgroundColor: "skyblue" }}>
          <p>Component B</p>
          {props.children}
        </div>
      );
    }
    
    function CommonComponent() {
      React.useEffect(() => {
        console.log("useEffect is running in CommonComponent.");
      }, []);
      return (<div>Common component.</div>);
    }
    
    function App() {
      const [ab, setAB] = React.useState(true);
      const ParentComponent = ab ? ComponentA : ComponentB;
      return (
        <React.Fragment>
          <label>
            <input
              type="checkbox"
              checked={ab}
              onChange={(event) => {
                setAB(event.target.checked);
              }}
            />
            Switch A/B
          </label>
    
          <ParentComponent>
            <CommonComponent />
          </ParentComponent>
        </React.Fragment>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      rootElement
    );
[/code]

[code]

    .App {
      font-family: sans-serif;
      text-align: center;
    }
[/code]

[code]

    <script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>
[/code]

「Switch A/B」を切り替えるたびに、 CommonComponent 内の useEffect が発火するため、コンソールに "useEffect
is running in CommonComponent." と出力されます。  
このとき、実際のアプリケーションではサーバーからデータの fetch を行っており、実行コストが高いです。  
この useEffect は依存リストに空配列を設定している通り、本質的には親の影響を受けない処理内容のため、何らかの方法で useEffect
の再実行を回避できないでしょうか？

なお、実際には、 ComponentA, B はフォームであり、 CommonComponent はいくつかのチェックボックスをまとめたフォームの一部です。


CC BY-SA 4.0
2021-02-10T07:17:12.547
73911
2021-04-26T19:16:15.967
2021-04-26T17:47:27.540
3068
43906
question
reactjs react-jsx
子コンポーネントのuseEffectを発火させないようにするには？


既に解決済みの問題かもしれませんが、一応参考程度の回答を書かせて頂きます。

* * *

親コンポーネントが再レンダリングされた場合、子コンポーネントは必ず再レンダリングされるので、構造が兄弟コンポーネントに変えられない場合、そのままのコードでは難しいかと思います。

今回に関しては再レンダリングを防ぎたいわけではなく「重いfetchを一度だけ実行したい」とのことなので、[Recoil](https://recoiljs.org/)
や [Redux](https://redux.js.org/) などの状態管理ライブラリを導入する方法もあります。

ただ今回の場合、質問文に書かれてる情報だけでは

  1. `ParentComponent` の差し替えタイミングでは `useEffect` が実行されなくてよい
  2. `CommonComponent` (とその大元の親、サンプルでは `App` )がレンダリングされた場合は一度実行したい

とのことですので、この `useEffect` は本来 `App` が行う責務なのではないでしょうか？

であれば、以下の様に独自フック(Custom Hook)の `useCommonStatus()` などを定義し、 `ParentComponent` と
`CommonComponent` をレンダリングする責務のコンポーネント( `App` )で `useEffect` を行い、fetchしたデータを
`CommonComponent` に渡せばいいかと思います。

独自フックの作成 – React  
<https://ja.reactjs.org/docs/hooks-custom.html>

[code]

    // 独自フック
    function useCommonStatus() {
      const [common, setCommon] = React.useState(null);
    
      React.useEffect(() => {
        console.log("useEffect is running.");
    
        setTimeout(() => {
          console.log("useEffect completed.");
          setCommon([1, 2, 3, 4, 5]);
        }, 5000);
      }, []);
    
      return common;
    }
    
    function ComponentA(props) {
      return (
        <div style={{ backgroundColor: "pink" }}>
          <p>Component A</p>
          {props.children}
        </div>
      );
    }
    
    function ComponentB(props) {
      return (
        <div style={{ backgroundColor: "skyblue" }}>
          <p>Component B</p>
          {props.children}
        </div>
      );
    }
    
    function CommonComponent(props) {
      return (<div>Common component values: {props.common ? props.common.join() : 'Loading...'}</div>);
    }
    
    function App() {
      const common = useCommonStatus();
    
      const [ab, setAB] = React.useState(true);
      const ParentComponent = ab ? ComponentA : ComponentB;
      return (
        <React.Fragment>
          <label>
            <input
              type="checkbox"
              checked={ab}
              onChange={(event) => {
                setAB(event.target.checked);
              }}
            />
            Switch A/B
          </label>
    
          <ParentComponent>
            <CommonComponent common={common} />
          </ParentComponent>
        </React.Fragment>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>,
      rootElement
    );
[/code]

[code]

    .App {
      font-family: sans-serif;
      text-align: center;
    }
[/code]

[code]

    <script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>
[/code]


CC BY-SA 4.0
2021-04-26T19:10:52.867
75526
2021-04-26T19:16:15.967
2021-04-26T19:16:15.967
3068
3068
73911
answer