Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

useRef versus a file-scoped variable

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading