React - useState Hook
React - useState Hook
This is a React component named UseState
that uses the useState
hook to manage a count state variable.
The component renders a div
with a p
element that displays the current value of count
. It also renders four Button
components that allow the user to increment or decrement the count.
The handleAdd
, handleAnotherAdd
, and handleThirdAdd
functions are event handlers for the first three buttons, respectively. Each function updates the count state variable using setCount
, which takes a new value for the state variable as an argument.
handleAdd
and handleThirdAdd
use a functional update, where the new value of count
is calculated based on the previous value using a function passed to setCount
. The difference between handleAdd
and handleThirdAdd
is that handleAdd
passes the previous count
value as an argument to the function, while handleThirdAdd
creates a new newCount
variable to hold the updated count value.
All three event handlers also display an alert message that shows the current value of count
.
Finally, the component also imports an httpService
module and uses it to make an HTTP GET request to https://jsonplaceholder.typicode.com/todos/
with the current count value as a parameter.
import { useState } from "react";
import Button from "@/components/Button";
import httpService from "@/services/httpService";
export default function UseState() {
const [count, setCount] = useState(0);
function handleAdd() {
setCount((count) => count + 1);
console.log(count)
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
function handleAnotherAdd() {
setCount(count + 1)
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
function handleThirdAdd() {
const newCount = count + 1;
setCount(newCount);
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
return (
<div className="container mt-6">
<p>Count: {count}</p>
<Button
small
text="(count) => count + 1"
type="primary"
className="mr-2"
onClick={handleAdd}
/>
<Button
small
text="count + 1"
type="primary"
className="mr-2"
onClick={handleAnotherAdd}
/>
<Button
small
text="count + 1"
type="primary"
className="mr-2"
onClick={handleThirdAdd}
/>
<Button
small
text="count - 1"
type="primary"
onClick={() => setCount(count - 1)}
/>
</div>
);
}
Simple Update and Functional Update - Difference between handleAdd and handleAnotherAdd
Both handleAdd
and handleAnotherAdd
are event handlers for buttons in a React component that uses the useState
hook to manage state. The main difference between handleAdd
and handleAnotherAdd
is how they update the state variable and display the current count value.
In handleAdd
, the setCount
function is called with a functional update that takes the previous count value as an argument and returns the new count value by adding 1 to it. This approach ensures that the count
state is updated correctly even if multiple updates are triggered in rapid succession. After calling setCount
, handleAdd
displays an alert dialog that shows the previous count value.
In handleAnotherAdd
, the setCount
function is called with a simple update that adds 1 to the previous count value. Unlike handleAdd
, this approach does not guarantee that the count
state will be updated correctly in all cases. After calling setCount
, handleAnotherAdd
displays an alert dialog that shows the current count value, which is the updated value after calling setCount
.
So, in summary, handleAdd
updates the state using a functional update and shows the previous count value in the alert dialog, while handleAnotherAdd
updates the state using a simple update and shows the current count value in the alert dialog.
In React's useState
hook, there are two ways to update the state of a component:
-
Simple update: In a simple update, the new state value is provided directly to the
setState
function. For example, if you have a state variable calledcount
, you can update it using a simple update like this:setCount(42);
In this case, the
count
state is immediately set to the new value of 42. -
Functional update: In a functional update, the new state value is computed based on the previous state value. This is useful when you need to update the state based on the previous value in a way that is independent of the timing of state updates. For example, if you have a state variable called
count
, you can update it using a functional update like this:setCount((prevCount) => prevCount + 1);
In this case, the
setCount
function receives a function as an argument, which takes the previous state valueprevCount
as an argument and returns the new state value by adding 1 to it. This approach ensures that the new state value is based on the most recent previous state value, even if multiple state updates occur in rapid succession.
Functional updates are also useful for avoiding issues with stale state values. For example, if you have a state variable that depends on another state variable, you can use a functional update to ensure that you always get the latest value of the other variable:
setCount((prevCount) => prevCount + 1);
setTotalCount((prevTotalCount) => prevTotalCount + prevCount);
In this case, setTotalCount
uses a functional update that takes the previous value of prevCount
as an argument and adds it to the previous value of prevTotalCount
, ensuring that the state is updated correctly even if multiple state updates occur in rapid succession.
Common Pitfalls When Using useState in React
The following examples are related to the useState
hook in React. The first example demonstrates how the value of a state variable may be different at different points in time, while the second example shows how to update the state correctly using the useState
hook and the useEffect
hook in React.
While the examples may be different in terms of their specific use cases and implementation, they both involve the use of the useState
hook in React.
useState
is used to manage state within a component, and it provides a way to update the state asynchronously. When you call the useState
hook, it returns an array with two values: the current state value, and a function that you can use to update the state value. When you call the state update function, React schedules a re-render of the component with the updated state value. This update may not happen immediately, but instead may happen asynchronously, in a batched update.
Real example - Difference between "count" on screen and on alert
import { useState } from "react";
import Button from "@/components/Button";
import httpService from "@/services/httpService";
export default function UseState() {
const [count, setCount] = useState(0);
function handleAdd() {
setCount((count) => count + 1);
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
function handleAnotherAdd() {
setCount(count + 1)
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
function handleThirdAdd() {
const newCount = count + 1;
setCount(newCount);
alert("what is count:" + count);
httpService.get("https://jsonplaceholder.typicode.com/todos/" + count);
}
return (
<div className="container mt-6">
<p>Count: {count}</p>
<Button
small
text="(count) => count + 1"
type="primary"
className="mr-2"
onClick={handleAdd}
/>
<Button
small
text="count + 1"
type="primary"
className="mr-2"
onClick={handleAnotherAdd}
/>
<Button
small
text="count + 1"
type="primary"
className="mr-2"
onClick={handleThirdAdd}
/>
<Button
small
text="count - 1"
type="primary"
onClick={() => setCount(count - 1)}
/>
</div>
);
}
What happen between the count on the alert and the count variable on Count: {count}
?
In React, when you call the useState
hook, it returns a pair of values: the current state value and a function that allows you to update the state. In this component, the count
state variable is being updated by the setCount
function, which is called in response to the handleAdd
, handleAnotherAdd
, and handleThirdAdd
functions.
When the component renders, the current value of count
is used to render the Count
component that displays the count value on the screen. So, when you see the Count
component being displayed with a value of, say, 5, it means that the count
state variable has been updated to a value of 5.
On the other hand, when you see an alert pop up with the value of count
, it is showing the value of count
at the time the alert
function is called. It is important to note that the alert
function does not block the execution of the rest of the code, so the value of count
may have changed by the time you dismiss the alert. The value of count
on the Count
component and the value of count
in the alert may not be the same, since they may have been updated at different times.
So probably count will show 4 at alert section and show 5 on the screen
Yes, that's correct! If the current value of count
is 4 and you click the button that calls handleAdd
, the setCount
function will be called with a new value of count + 1
, which is 5. However, at the time the alert
function is called, the value of count
is still 4. So the alert will show what is count: 4
. After the alert is dismissed, the httpService.get
function is called with a URL that includes the current value of count
, which is now 5. Finally, the Count
component will re-render with the updated value of count
, which is now 5.
Real example - shipping address2 will accidentally prefill the billing address 2
Daniel: When I fill billing address, apt/suite for billing address has already filled. I don't think that should be happening, usually if its a different billing address. It also mean that it will probably be a different address altogher, no way address2 will be the same or it will be very rare to have two different addresses with same suite
Me: There appears to be a problem with the checkbox indicating that the billing address matches the shipping address.
Daniel pointed out that when he fills out the billing address, the apt/suite for billing address is automatically prefilled. This shouldn't happen because the billing address is often different from the shipping address, and it's unlikely for two different addresses to have the same suite.
I suspect that the issue is with the checkbox that indicates whether the billing address matches the shipping address. The code snippet below shows that when the checkbox is toggled, the status of sameAddress
is not being updated correctly. The console logs show that the status is always false, even after toggling.
const handleClickSameAddress = () => {
console.log("The status of billing address is the same as the shipping address (before toggle):", sameAddress);
setSameAddress((prev) => !prev);
console.log("The status of billing address is the same as the shipping address (after toggle):", sameAddress);
};
The result here will be as the following
The status of billing address is the same as the shipping address (before toggle): false
The status of billing address is the same as the shipping address (after toggle): false
The issue occurs when toggling the status of the billing address checkbox. The current implementation of handleClickSameAddress
sets the state of sameAddress
to the opposite value of the previous state, and then checks the value of sameAddress
to decide whether to set the billing address the same as the shipping address or reset the state.
However, this implementation can result in unexpected behavior because the check for sameAddress
happens before the state has been updated. Therefore, if the checkbox is unchecked (i.e., sameAddress
is false
), the function will incorrectly set the billing address the same as the shipping address, and vice versa.
const handleClickSameAddress = () => {
setSameAddress((prev) => !prev);
if(sameAddress) {
// set the billing address the same as shipping address
} else {
// reset the state
}
};
We can use useEffect to correct the wrong behaviour mentioned above
In React, state updates are asynchronous, which means that the updated state value may not be immediately available after it has been set. In the provided code, setSameAddress
is called with the new value for sameAddress
, but it may not update the state immediately.
Therefore, when you log sameAddress
immediately after calling setSameAddress
, you may still see the previous value of sameAddress
. This is because React may batch state updates for performance reasons, and only apply them when it's necessary.
To ensure that you see the updated value of sameAddress
, you can use the useEffect
hook and add sameAddress
as a dependency. This will ensure that the console.log
statement runs after the state has been updated:
useEffect(() => {
console.log("The status of billing address is the same as the shipping address (after toggle):", sameAddress);
}, [sameAddress]);
This way, the console log statement will be executed every time sameAddress
changes, and you will see the updated value.
Also, we can use another way to fix that issue. The function should use the updated value of sameAddress
returned by the setSameAddress
function.
const handleClickSameAddress = () => {
setSameAddress((prev) => {
if (!prev) {
// set the billing address the same as the shipping address
} else {
// reset the state
}
return !prev;
});
};
Why useState
const Counter = () => {
let counter = 0;
const onClick = () => {
counter = counter + 1;
console.log(counter);
};
return (
<>
<button onClick={onClick}>click to update counter</button>
Counter value: {counter}
</>
);
};
This is not going to work of course. In our console.log
we’ll see the updated counter value, but the value rendered on the screen is not going to change - variables don’t cause re-renders, so our render output will never be updated.
useState, on the other hand, will work as expected: that’s exactly what state is for
usePrevious
This component demonstrates how custom hooks can be used to extend the functionality of React components and make them more reusable. The usePrevious
hook in particular can be useful for tracking changes in state variables and performing logic based on those changes.
import { useState, useEffect, useRef } from "react";
import Button from "@/components/Button";
const usePrevious = (value: any) => {
// create a new reference
const ref = useRef();
// store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect above)
return ref.current;
};
const UsePrevious = () => {
const [count, setCount] = useState(1);
// get the previous value passed into the hook on the last render
const prevCount = usePrevious(count);
// show both current and previous value
return (
<div className="container mt-6">
<div>
Now: {count}, before: {prevCount}
</div>
<Button
small
text="add"
type="primary"
className="mr-2"
onClick={() => setCount(count + 1)}
/>
</div>
);
};
export default UsePrevious;
The code provided is a React component that demonstrates the use of a custom hook called usePrevious
. This hook allows the component to keep track of the previous value of a state variable, so that it can be used for comparison or other purposes.
In the component, the useState
hook is used to create a state variable count
with an initial value of 1. The usePrevious
hook is then called to create a reference to the previous value of count
.
The usePrevious
hook works by using the useEffect
hook to store the current value of count
in a ref
object. This useEffect
hook is only re-run when count
changes, so the ref
object will always contain the previous value of count
.
Finally, the component renders a div that displays both the current value of count
and its previous value. Whenever the "add" button is clicked, the setCount
function is called to update the value of count
, and the usePrevious
hook is used to retrieve the previous value of count
and display it on the screen.
Why it can get previous data from by assigning the count value to ref.current. That's puzzling
When the component mounts, useEffect
runs and assigns the initial value of count
to the ref
. As the component re-renders, the useEffect
function runs again, but this time with the new value of count
. Since the ref is being updated with the new value of count
, prevCount
always contains the previous value of count
before it was updated.
In React, useRef
creates a mutable object that can store a value and persists across renders. The useEffect
hook runs after every render of the component and allows for side effects, like updating a ref
. Since useEffect
runs after the render, prevCount
will contain the previous value of count
from the previous render. This is how prevCount
is able to hold the previous value of count
and is returned by usePrevious
hook.
The answer from stack overflow:
While this technically works, it's a confusing way of doing it and may lead to bugs as you add more stuff. The reason it works is because useEffect
runs after state changes, and changing ref values don't cause a re-render. The way the code you posted works is as follows:
- Initially, both would be empty
- User types something, triggering a state change via
setValue
- This triggers a re-render, so
{value}
is the new value, but since the ref hasn't been updated yet,{prevValue.current}
still renders as the old value - Next, after the render, the effect runs, since it has
value
as a dependency. So this effect updates the ref to contain the CURRENT state value - However, since changing a ref value doesn't trigger a re-render, the new value isn't reflected in what's rendered
So once those steps above finish, then yes technically the state value and the ref are the same value. However, since the ref change didn't trigger a re-render, it still shows the old ref value in what's rendered.
This is obviously not great, cos if something else triggers a re-render, like say you had another input with a connected state value, then yes the {prevValue.current}
would then re-render as the current {value}
and then technically be wrong cos it would show the current value, not previous value.
I really don't know how ref.current = value
works
useRef
returns an object with a single property called current
, which is initialized to the initialValue
passed to useRef()
.
When useRef
is called inside a component, it returns the same ref object on every render. By changing the current
property of this ref object, you can store any mutable value that you want to preserve between re-renders.
In the code you provided, ref.current
is assigned the value of value
passed to usePrevious
hook. This value is then persisted across re-renders, allowing you to access it in subsequent renders. This is why you are able to retrieve the previous value of the count
state using the prevCount
variable.
How do it persisted across re-renders
The value of ref.current
is persisted across re-renders because the useEffect
hook is used with a dependency array that includes the value
argument. When the value
changes, the effect callback is triggered, which updates the value of ref.current
to the new value
.
Since the useEffect
hook only runs after the render phase, the previous value is always available on the next render. This allows the hook to store and retrieve the previous value of the value
argument every time the component is re-rendered, even though the value
variable is reset to a new value on every render.
lazy initializer
In React, a lazy initializer is a function that is used to initialize state lazily, meaning that the state is not immediately set when the component is mounted. Instead, the state is set when it is first accessed.
Lazy initialization is commonly used with the useState
hook, which is a way to add state to a functional component in React. When using the useState
hook, you can provide a lazy initializer as the initial value for the state.
Here is an example of using a lazy initializer with the useState
hook:
import { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(() => {
console.log("Initializing count...");
return 0;
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In this example, the lazy initializer is the arrow function () => { console.log("Initializing count..."); return 0; }
. This function is only called the first time the count
state is accessed, which is when the component is first rendered. The function sets the initial value of count
to 0
and logs a message to the console.
Using a lazy initializer can be useful for expensive calculations or operations that are only needed in certain cases. By using a lazy initializer, you can delay the execution of these operations until they are actually needed, which can help improve performance.