🔑

【Reactクイズ】stateは保持される?リセットされる?

2024/11/10に公開

全部で10問です。

以降の問の中で各Counterはstateを持っています。チェックボックスでCounterコンポーネントの表示切替をしたときのstateの状態を当てるクイズになります。もしコンポーネントの表示を切り替えをしてもstateが保持される場合は「保持される」、リセットされる場合は「リセットされる」と回答してください。

↓画面イメージ

バージョン
"react": "^18.3.1",
"react-dom": "^18.3.1",

問1

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? <Counter isRed={true} /> : <Counter isRed={false} />}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

保持される
Appから見たCounterの位置に変化がないから

問2

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? <Counter isRed={true} /> : null}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

リセットされる
Counterコンポーネントがアンマウントされるから

問3

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? <Counter isRed={true} /> : <Counter isRed={false} />}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  if (isRed) {
    return (
      <div>
        <h1 style={style}>{count}</h1>
        <p>赤文字カウンター</p>
        <button onClick={() => setCount(count + 1)}>countup</button>
      </div>
    );
  } else {
    return (
      <div>
        <h1 style={style}>{count}</h1>
        {null}
        <button onClick={() => setCount(count + 1)}>countup</button>
      </div>
    );
  }
}
答え

保持される
Appから見たCounterの位置に変化がないから

問4

import { useState } from "react";

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? <Counter isRed={true} /> : <Counter isRed={false} />}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? "red" : undefined,
  };

  if (isRed) {
    return (
      <div>
        <p>赤文字カウンター</p>
        <button onClick={() => setCount(count + 1)}>countup</button>
        <h1 style={style}>{count}</h1>
      </div>
    );
  } else {
    return (
      <section>
        <h1 style={style}>{count}</h1>
        <button onClick={() => setCount(count + 1)}>countup</button>
      </section>
    );
  }
}

答え

保持される
Appから見たCounterの位置に変化がないから

問5

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? (
        <div>
          <Counter isRed={true} />
        </div>
      ) : (
        <section>
          <Counter isRed={false} />
        </section>
      )}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

リセットされる
Appから見てdivがsectionに変化している場合、その要素の配下にある要素のコンポーネントは全てアンマウントされたあと再度レンダーされるから

問6

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed && <Counter isRed={true} />}
      {!isRed && <Counter isRed={true} />}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

リセットされる
Appから見たCounterの位置が変化しているから

問7

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? (
        <div>
          <p>赤文字カウンター</p>
          <Counter isRed={true} />
        </div>
      ) : (
        <div>
          <Counter isRed={false} />
        </div>
      )}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

リセットされる
Appから見たCounterの位置が変化しているから

問8

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? (
        <div>
          <p>赤文字カウンター</p>
          <Counter isRed={true} />
        </div>
      ) : (
        <div>
          {null}
          <Counter isRed={false} />
        </div>
      )}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

保持される
Appから見たCounterの位置が変化していないから

問9

import { useState } from 'react';

export default function App() {
  const [isRed, setIsRed] = useState(false);
  return (
    <div>
      {isRed ? <Counter key="red" isRed={true} /> : <Counter key="none" isRed={false} />}
      <input
        type="checkbox"
        checked={isRed}
        onChange={(e) => {
          setIsRed(e.target.checked);
        }}
      />
    </div>
  );
}

function Counter({ isRed }: { isRed: boolean }) {
  const [count, setCount] = useState(0);

  const style = {
    color: isRed ? 'red' : undefined,
  };

  return (
    <div>
      <h1 style={style}>{count}</h1>
      <button onClick={() => setCount(count + 1)}>countup</button>
    </div>
  );
}
答え

リセットされる
keyが異なるから

問10

姓と名のテキストフィールドとチェックボックスがあります。チェックボックスを押下することで1と2の表示を切り替えることができます。
1:lastNameが下、firstNameが上の表示
2:lastNameが上、firstNameが下の表示

各テキストフィールドそれぞれは入力をstateとして保持しています。

では表示を切り替えたとき状態は保持されますか?リセットされますか?

↓画面イメージ

import { useState } from 'react';

export default function App() {
  const [reverse, setReverse] = useState(false);
  const checkbox = (
    <label>
      <input type="checkbox" checked={reverse} onChange={(e) => setReverse(e.target.checked)} />
      Reverse order
    </label>
  );
  if (reverse) {
    return (
      <>
        <Field key="lastName" label="Last name" />
        <Field key="firstName" label="First name" />
        {checkbox}
      </>
    );
  } else {
    return (
      <>
        <Field key="firstName" label="First name" />
        <Field key="lastName" label="Last name" />
        {checkbox}
      </>
    );
  }
}

function Field({ label }: { label: string }) {
  const [value, setValue] = useState('');
  return (
    <div>
      <label>{label}</label>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}
答え

保持される
補足としてkey propsを削除したとしても保持されます。ただしstateはkeyではなくコンポーネントの位置に紐づいているのでlastNameに値を入力したあと切り替えるとfirstNameにlastNameの値が入るので注意してください。

以上です。

ポイント

stateの保持ルール
stateはレンダーツリーの位置に保持されます。コンポーネントはstateを持っていません。コンポーネントの外でReactがstateを管理しています。

stateのリセットタイミング

  1. コンポーネントのアンマウント
  2. 親の要素の変化
  3. 位置の変化
  4. keyの変化

Discussion