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