Home > database >  useRef versus a file-scoped variable
useRef versus a file-scoped variable

Time:10-07

I've been reading on why useRef is useful (e.g. in this SO answer and in the articles it links to) and it does make sense to me. However I notice that in my code I've "simply" solved the issue of how to store state in a functional component in a way that does not trigger re-renders by keeping the state as a global-scoped variable declared in the same file as the functional component.

I realize this isn't appropriate if the same component is rendered at the same time in multiple places on the DOM, as useRef supplies different state to different simultaneously rendered components whereas a file-scoped variable would be shared.

Is my mental model and assumptions correct and are there any other use cases or distinct advantages of useRef versus a file-scoped variable?

CodePudding user response:

Is my mental model and assumptions correct ...

Not in the general case, even with your caveat. There are a couple of issues:

  1. As you say, components can be instantiated more than once at the same thing (think: items in a list), but with your file-scoped (I assume you mean module-scoped) variable, all instances would use the same variable, causing cross-talk between the instances. With useRef, they'll each have their own non-state instance data.

    Here's an example of the difference:

       
       const { useState, useRef } = React;
       
       let moduleScopedVariable = 0;
       
       const TheComponent = () => {
           const ref = useRef(0);
       
           // Synthetic use case: counting renders
             ref.current;
             moduleScopedVariable;
       
           return (
               <div className="the-component">
                   <div>Render count (ref): {ref.current}</div>
                   <div>Render count (var): {moduleScopedVariable}</div>
               </div>
           );
       };
       
       const ids = [0, 1, 2];
       
       const Example = () => {
           const [counter, setCounter] = useState(0);
       
           return (
               <div>
                   Counter: {counter}
                   <input type="button" value="Increment" onClick={() => setCounter(c => c   1)} />
                   <div>{ids.map((id) => <TheComponent key={id} />)}</div>
               </div>
           )
       };
       
       const root = ReactDOM.createRoot(document.getElementById("root"));
       root.render(<Example />);
       
       
       
       .the-component {
           border: 1px solid black;
           margin: 4px;
       }
       
       
       
       <div id="root"></div>
       
       <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
       
       

  2. It's not just at the same time, there's crosstalk with a single instance that's mounted and unmounted over time:

    Here's an example:

       
       const { useState, useRef } = React;
       
       let moduleScopedVariable = 0;
       
       const TheComponent = () => {
           const ref = useRef(0);
       
           // Synthetic use case: counting renders
             ref.current;
             moduleScopedVariable;
       
           return (
               <div className="the-component">
                   <div>Render count (ref): {ref.current}</div>
                   <div>Render count (var): {moduleScopedVariable}</div>
               </div>
           );
       };
       
       const Example = () => {
           const [flag, setFlag] = useState(true);
       
           return (
               <div>
                   Flag: {String(flag)}
                   <input type="button" value="Toggle" onClick={() => setFlag(b => !b)} />
                   {flag && <TheComponent />}
               </div>
           )
       };
       
       const root = ReactDOM.createRoot(document.getElementById("root"));
       root.render(<Example />);
       
       
       
       .the-component {
           border: 1px solid black;
           margin: 4px;
       }
       
       
       
       <div id="root"></div>
       
       <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
       
       

  3. It also means the data remains lying around when the component is unmounted, which depending on what the data is could be an issue.

Fundamentally, it's great to reuse static data by closing over a module-scoped constant, but anything that changes within the component should be stored in state (in any of various guises) if it affects how the component renders, or a ref (usually) if not.

  • Related