In this article, we'll delve into a thorough comparison of React state
and refs
, exploring their suitability for specific scenarios.
When faced with the need to store data in your React application, the first question that comes to mind is: "Will the data change at some point during the component's lifecycle?" If it won't, a regular const
variable is well-suited.
However, if the data will change, then that is where the useState
and useRef
hooks comes in.
Understanding the useState and useRef Hooks
useState Hook
The useState
hook is designed to manage a component's state, which represents data that can change over time and is important for the component to render. You can add state to your component by importing the useState
hook from React.
import { useState } from 'react';
The useState
hook is usually initialized with an initial value and returns an array of a declared state variable and its associated setter function. It looks something like this:
import { useState } from "react";
function App() {
const [count, setCount] = useState(0); //declared useState hook
return (
<>
<h1>State example</h1>
<div>
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</div>
</>
);
}
export default App;
In the above code,
- The
useState
is initialized with a value of zero and returns acount
variable andsetCount
setter function. - The
count
variable is set dynamically by thesetCount
setter function that increments thecount
by 1. - For each click of the button, the
App
component is re-rendered and the updated value is displayed within the button text.
Having a good understanding of React state is important as it is one of the most used concept. You can have a more in-depth read on states here: State Management in React.
useRef Hook
The useRef
hook is used to create refs in React components. A ref is an object with a current
property that holds a value. It basically references a DOM element or an instance of a component. We can read and update the value by accessing the current property.
const ref = useRef(initialValue)
ref.current = initialValue
Here, is a full code snippet of ref in action:
import { useRef } from "react";
function App() {
let ref = useRef(0);
function handleIncrease() {
ref.current++;
alert(`You have clicked it ${ref.current} times`);
}
return (
<>
<h1>Ref example</h1>
<div>
<button onClick={handleIncrease}>Click Me</button>
</div>
</>
);
}
export default App;
Let's break it down:
- We imported
useRef
from React. - In our
App
component, we declared aref
object with the initial value set to zero. handleIncrease
is our handler function that increments theref.current
value by 1 and then alerts the user of the current value.- In our
App
component's JSX, we have a button with anonClick
prop and thehandleIncrease
handler function passed to it.
Having understood how the two hooks work, we'll go ahead to compare and explore when they would be suitable to use.
React State vs Ref
Render Trigger
In React, states always trigger a re-render due to a mechanism known as reconciliation
– which updates the user interface based on changes made to the state or props.
Under the hood, React compares the new state to the previous and computes the minimal changes needed to update the user interface that reflects the new state. This process ensures consistency with the changed state or props.
On the flip side, refs do not trigger a re-render when changes are made to it. Refs are not directly linked to the component's rendering cycle.
Therefore, if you want a consistent user interface that reacts to data changes, it is advisable to use states. Refs are better used for managing mutable values without affecting the user interface.
Mutability
React state cannot be directly changed once it has been set because the setter function updates of the state variable. By using this approach, React maintains the predictability and stability of the data flow. This also helps in making debugging easier.
Conversely, refs are mutable as you can modify the ref current
value outside of the rendering process. Values can be changed at any point unlike states – refs do not have an updater function.
Read/Write Operations
The useState
hook setter function allows you to update the state value. For instance:
const [state, setState] = useState(false)
function handleOpposite(){
setState(!state)
}
In this code, we can see that:
- The initial value is set to a boolean value of
false
. - The
handleOpposite
function is negating the boolean value of thestate
and thesetState
houses the updated value oftrue
.
In this simple operation,
- An implicit read operation has been done as the initial value had to be accessed before the negation.
- A write operation occurred when the negation (!) was used on the initial value, which changed the value to the opposite.
An explicit read operation of state happens when you simply access the state variable directly within a component's JSX. For instance:
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
The {count}
is the currently accessed value of the state and would be displayed on the UI accordingly.
On the other hand, accessing or modifying a ref
's current value during rendering can interfere with React's reconciliation process, potentially causing inconsistencies between the virtual DOM and the actual DOM.
In order to ensure predictable behavior and optimal performance in components, it's best to adhere to React's guidelines and avoid either accessing or modifying refs during rendering.
Persisting Across Renders
Data persistence across renders in React means that data remains consistent and available between different render cycles of a component. When data is persisted, it remains unchanged and accessible after re-rendering. State and Ref both persist data across renders.
Persistence is crucial for maintaining the integrity of the application's state and ensures that components operate as expected.
Asynchronous Updates
Updates in React state are asynchronous which means that when there is a request for an update, there is a possibility that the updates will not be executed immediately. React might leave some state changes for later while updating other components in one go!
Ref updates are done synchronously where tasks are performed in a sequential manner. Each task waits for the previous one to finish before starting, ensuring that they are executed in a predictable and deterministic manner.
Conclusion
In this article, we looked extensively at the hooks — useState
and useRef
— that handle dynamic data (data that will change) in React applications.
We compared both hooks and at this point, you should be know their similarities, differences, when and where they are best suited.
Connect with me on LinkedIn for front-end related discussions and posts. See you on the next one!