👭

同じcomponentを条件分岐でrenderするとunmountされない

2022/08/01に公開

問題

componentが思いがけず再renderされないシチュエーションに遭遇したので記事書きます。

import React from 'react';

const Wrapper: React.FC = props => {
  React.useEffect(() => {
    console.log('mount Wrapper');

    return () => console.log('unmount Wrapper');
  }, [])
  return(
    <div>{props.children}</div>
  )
}

const Test: React.FC = (props) => {
  const [condition, setCondition] = React.useState(true);
  return (
    <div>
      {condition ? (
        <Wrapper>AAA</Wrapper>
      ) : (
        <Wrapper>BBB</Wrapper>
      )}
      <button onClick={e => setCondition(!condition)}>change</button>
    </div>
  );
};

export default Test;

このTestを適当な場所にrenderしてchangeボタンを繰り返し押すとAAABBBが交互に表示されますが、consoleの表示はどうなると思います?毎回Wrapperが作り直されて下記のように表示されると思いませんか?

mount Wrapper
# ボタン押す
unmount Wrapper
mount Wrapper
# ボタン押す
unmount Wrapper
mount Wrapper
...

私は思ったのですがmount Wrapperが一度だけ表示されたきり、何度押しても何も出ません。つまりunmountされません。

解決策

  return (
    <div>
      {condition ? (
        <Wrapper key="AAA">AAA</Wrapper>
      ) : (
        <Wrapper key="BBB">BBB</Wrapper>
      )}
      <button onClick={e => setCondition(!condition)}>change</button>
    </div>
  );

keyをつけてやれば毎回新しくrenderされます。

これ、気づいてみれば、条件によって<Wrapper>AAA</Wrapper><Wrapper>BBB</Wrapper>が評価されるだけなので、react的にはchildrenが変わっただけと扱われるのは何となく分からなくはないですが、<Wrapper />が二つ別々に書いてあるので、別物のような気がしました。

可能な限り使い回すようにできてるんですね。そう考えると凄いな〜と感心します。

Discussion