React: 1つの state に対して複数箇所で useEffect / setState すると最終的に有効なのはどの値か

React: 1つの state に対して複数箇所で useEffect / setState すると最終的に有効なのはどの値か

February 21, 2022

試行その1 #

import { useState, useEffect } from 'react';

export default function Component() {
  const [int, setInt] = useState<number>();

  useEffect(() => setInt(1), []);
  useEffect(() => setInt(3), []);
  useEffect(() => setInt(2), []);

  console.log(int);

  return <></>;
}

結果その1 #

コンソールには以下の順番で出力されます。

undefined
2

初回レンダリングは undefined となっているとして、次のレンダリングでは最後に指定された setState の結果が有効となるようです。

試行その2 #

子のコンポーネントでも setState するとどうなるでしょうか。

import { useState, useEffect } from 'react';

export default function Parent() {
  const [int, setInt] = useState<number>();

  useEffect(() => setInt(1), []);
  useEffect(() => setInt(3), []);
  useEffect(() => setInt(2), []);

  console.log(`Parent ${int}`);

  return <Child int={int} setInt={setInt} />;
}

type Props = {
  int: number | undefined;
  setInt: React.Dispatch<React.SetStateAction<number | undefined>>;
};

function Child(props: Props) {
  const { int, setInt } = props;

  useEffect(() => setInt(10), []);
  useEffect(() => setInt(30), []);
  useEffect(() => setInt(20), []);

  console.log(`Child ${int}`);

  return <></>;
}

結果その2 #

コンソールには以下の順番で出力されます。

Parent undefined
Child undefined
Parent 2
Child 2

最終結果は 20 になると思っていたので予想外でした。

試行その3 #

どの順番で useEffect が動いているかを確認してみます。

import { useRef, useEffect } from 'react';

export default function Parent() {
  const int = useRef<number[]>([]);

  useEffect(() => {
    int.current.push(1);
  }, []);

  useEffect(() => {
    int.current.push(3);
  }, []);

  useEffect(() => {
    int.current.push(2);
  }, []);

  setTimeout(() => {
    console.log(int.current);
  }, 1000);

  return <Child int={int} />;
}

function Child({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(10);
  }, []);

  useEffect(() => {
    int.current.push(30);
  }, []);

  useEffect(() => {
    int.current.push(20);
  }, []);

  return <></>;
}

結果その3 #

コンソールには以下の順番で出力されます。

[]
[10, 30, 20, 1, 3, 2]

試行その4 #

孫コンポーネントまで用意し同じことを試してみます。

import { useRef, useEffect } from 'react';

export default function Parent() {
  const int = useRef<number[]>([]);

  useEffect(() => {
    int.current.push(1);
  }, []);

  useEffect(() => {
    int.current.push(3);
  }, []);

  useEffect(() => {
    int.current.push(2);
  }, []);

  setTimeout(() => {
    console.log(int.current);
  }, 1000);

  return <Child int={int} />;
}

function Child({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(10);
  }, []);

  useEffect(() => {
    int.current.push(30);
  }, []);

  useEffect(() => {
    int.current.push(20);
  }, []);

  return <Grandchild int={int} />;
}

function Grandchild({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(100);
  }, []);

  useEffect(() => {
    int.current.push(300);
  }, []);

  useEffect(() => {
    int.current.push(200);
  }, []);

  return <></>;
}

結果その4 #

コンソールには以下の順番で出力されます。

[]
[100, 300, 200, 10, 30, 20, 1, 3, 2]

試行その5 #

親は2つの子を持ち、子は2つの孫を持つ状態で実行してみます。

import { useRef, useEffect } from 'react';

export default function Parent() {
  const int = useRef<number[]>([]);

  useEffect(() => {
    int.current.push(1);
  }, []);

  setTimeout(() => {
    console.log(int.current);
  }, 1000);

  return (
    <>
      <Child1 int={int} />;
      <Child2 int={int} />;
    </>
  );
}

function Child1({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(10);
  }, []);

  return (
    <>
      <Grandchild11 int={int} />;
      <Grandchild12 int={int} />;
    </>
  );
}

function Child2({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(20);
  }, []);

  return (
    <>
      <Grandchild21 int={int} />;
      <Grandchild22 int={int} />;
    </>
  );
}

function Grandchild11({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(110);
  }, []);

  return <></>;
}

function Grandchild12({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(120);
  }, []);

  return <></>;
}

function Grandchild21({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(210);
  }, []);

  return <></>;
}

function Grandchild22({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(220);
  }, []);

  return <></>;
}

結果その5 #

コンソールには以下の順番で出力されます。

[]
[110, 120, 10, 210, 220, 20, 1]

試行その6 #

しつこくなってきたのでこれで最後です。親は2つの子を持ち、子は2つの孫を持ち、孫は2つのひ孫を持つ状態で実行してみます。

import { useRef, useEffect } from 'react';

export default function Parent() {
  const int = useRef<number[]>([]);

  useEffect(() => {
    int.current.push(1);
  }, []);

  setTimeout(() => {
    console.log(int.current);
  }, 1000);

  return (
    <>
      <Child1 int={int} />;
      <Child2 int={int} />;
    </>
  );
}

function Child1({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(10);
  }, []);

  return (
    <>
      <Grandchild11 int={int} />;
      <Grandchild12 int={int} />;
    </>
  );
}

function Child2({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(20);
  }, []);

  return (
    <>
      <Grandchild21 int={int} />;
      <Grandchild22 int={int} />;
    </>
  );
}

function Grandchild11({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(110);
  }, []);

  return (
    <>
      <GreatGrandchild111 int={int} />
      <GreatGrandchild112 int={int} />
    </>
  );
}

function Grandchild12({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(120);
  }, []);

  return (
    <>
      <GreatGrandchild121 int={int} />
      <GreatGrandchild122 int={int} />
    </>
  );
}

function Grandchild21({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(210);
  }, []);

  return (
    <>
      <GreatGrandchild211 int={int} />
      <GreatGrandchild212 int={int} />
    </>
  );
}

function Grandchild22({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(220);
  }, []);

  return (
    <>
      <GreatGrandchild221 int={int} />
      <GreatGrandchild222 int={int} />
    </>
  );
}

function GreatGrandchild111({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(1110);
  }, []);

  return <></>;
}

function GreatGrandchild112({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(1120);
  }, []);

  return <></>;
}

function GreatGrandchild121({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(1210);
  }, []);

  return <></>;
}

function GreatGrandchild122({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(1220);
  }, []);

  return <></>;
}

function GreatGrandchild211({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(2110);
  }, []);

  return <></>;
}

function GreatGrandchild212({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(2120);
  }, []);

  return <></>;
}

function GreatGrandchild221({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(2210);
  }, []);

  return <></>;
}

function GreatGrandchild222({ int }: { int: React.MutableRefObject<number[]> }) {
  useEffect(() => {
    int.current.push(2220);
  }, []);

  return <></>;
}

結果その6 #

コンソールには以下の順番で出力されます。

[]
[1110, 1120, 110, 1210, 1220, 120, 10, 2110, 2120, 210, 2210, 2220, 220, 20, 1]

まとめ #

最後の試行結果を視覚化して終わりとします。

以下の階層構造・順番でコンポーネントが構成されており、それぞれで useEffect を実行している場合は、

Parent
 ├ Child1
 │  ├ Grandchild11
 │  │  ├ GreatGrandchild111
 │  │  └ GreatGrandchild112
 │  └ Grandchild12
 │     ├ GreatGrandchild121
 │     └ GreatGrandchild122
 └ Child2
    ├ Grandchild21
    │  ├ GreatGrandchild211
    │  └ GreatGrandchild212
    └ Grandchild22
       ├ GreatGrandchild221
       └ GreatGrandchild222

以下の順番で useEffect が実行されます。

GreatGrandchild111
GreatGrandchild112
Grandchild11
GreatGrandchild121
GreatGrandchild122
Grandchild12
Child1
GreatGrandchild211
GreatGrandchild212
Grandchild21
GreatGrandchild221
GreatGrandchild222
Grandchild22
Child2
Parent