reactjs

position of a component in JSX curly braces in the UI tree


import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

According to the description in React official document(https://react.dev/learn/preserving-and-resetting-state#option-1-rendering-a-component-in-different-positions), the two Counter components in the code above are located in different positions in the UI tree.

However, I think the two Counter components are always in the position of <div><Counter /><button /></div> no matter what the isPlayerA state value is.

How do the two Counter components exist in different positions in the UI tree?

-----------------------------------------------------------------------------------------------

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

I expected it to work like the code above.


Solution

  • According to the description in React official document(https://react.dev/learn/preserving-and-resetting-state#option-1-rendering-a-component-in-different-positions), the two Counter components in the code above are located in different positions in the UI tree.

    However, I think the two Counter components are always in the position of no matter what the isPlayerA state value is.

    I think the thing you're missing is that false is a value. When you render this:

    <div>
      {isPlayerA && <Counter person="Taylor" />}
      {!isPlayerA && <Counter person="Sarah" />}
      <button
        onClick={() => {
          setIsPlayerA(!isPlayerA);
        }}
      >
        Next player!
      </button>
    </div>
    

    ... the div always has 3 children. It's either <Counter>, false, <button>; or false, <Counter>, <button>. So the counter moves from being the first child to the second child.

    With this:

    <div>
      {isPlayerA ? <Counter person="Taylor" /> : <Counter person="Sarah" />}
      <button
        onClick={() => {
          setIsPlayerA(!isPlayerA);
        }}
      >
        Next player!
      </button>
    </div>
    

    ... the div always has 2 children; a <Counter> and a <button>