skip to Main Content

I am currently learning react and have stumbled upon a section in their ‘tutorial’ section called ‘Describing the UI’. I can’t grasp my mind around understanding a subsection called ‘Keeping components pure’.
The thing I don’t understand is this:

Given this code:

let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

Why does the output look like this:
Tea cup for guest #2
Tea cup for guest #4
Tea cup for guest #6

And not like this:
Tea cup for guest #1
Tea cup for guest #2
Tea cup for guest #3

I tried to figure it out myself but I can’t.

2

Answers


  1. In development mode React renders your component twice, this is usefull for debugging (makes it easier to find unwanted behaviors, most obvious use case would arguably be useEffect()).

    I suspect you have a <React.StrictMode/> element in your index.js, that’s the component responsible for this behavior, but i would advise against removing it.

    If a render is a step then all calculation must be step-relevant, i would definitely advise against having an outside variable used in one of your components, the "right" way to do that would be to have another component responsible for rendering <Cup/> and passing the value as a prop.

    Login or Signup to reply.
  2. This likely happens becuase, in development mode, React renders everything twice on mount. You can witness this by adding the following into Cup:

    useEffect(() => {
      console.log('cup');
    }, []);
    

    Even though the useEffect has no dependencies and should only run once, you would see "cup" printed twice per Cup.

    With that in mind, each Cup component is being rendered twice. You have three, and therefore the final render displays the result after the second render for each, settling on 2, 4, and 6.

    However, keeping state in a global variable like this is not the move, and leads to unexpected behaviour. Given that, as you mention, Cup should be a "pure" component, its dependency on the global variable guest is counter-intuitive.

    A better approach would be to tell Cup to take some input, and always display that input. This is called props (short for properties), and allows you to render your cups purely based on a state, which is managed elsewhere. A serving suggestion:

    function Cup({ guest }) {
      // we destructure the guest from the props
      return <h2>Tea cup for guest #{guest}</h2>;
    }
    
    export default function TeaSet() {
      return (
        <>
          <Cup guest={1} />
          <Cup guest={2} />
          <Cup guest={3} />
        </>
      );
    }
    

    This opens up the possibility of keeping a state of guests in TesSet then looping over it. A more advanced example:

    function TeaSet() {
      const [guests, setGuests] = useState([1, 2, 3]);
      return (
        <>
          {guests.map(guest => <Cup key={guest} guest={guest} />)}
        </>
      );
    }
    

    This allows you to manipulate the state from within TeaSet without it affecting individual cups for existing guests, and without relying on any global variable, which may be handled in unexpected ways.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search