Setting state correctly using the State Hook - javascript

I'm currently learning React and trying to understand how to update the state correctly in a function component.
I've learned from the "State and Lifecycle" section in the React docs that you should update the state if it's using the previous state like the following:
// Correct
this.setState((state, props) => ({
counter: state.counter + 1
}));
instead of
// Wrong
this.setState({
counter: this.state.counter + 1,
});
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state. (Reference)
Now I want to achieve the same with a function component using the State Hook useState.
As an example how to update the state in a function component the React docs has the following code snippet:
<button onClick={() => setCount(count + 1)}>
Click me
</button>
which is equivalent to the following code in a class component:
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
But isn't this the "wrong" solution because it relies directly on the state?

But isn't this the "wrong" solution because it relies directly on the state?
setState merges the new state into the old one. And does it in a 'piecemeal' fashion e.g. new state can contain a small subset of old state's data. So it's beneficial to have the old state at hand inside the setState body and explicitly merge it using this.setState(oldState => ({ ...oldState, someProp: true }) )
'Explicitly' is better because it shows you don't have illusions about what setState will do anyway: merging even if you didn't.
useState replaces the old state with the new one so the need to have access to oldState is less acute. Even though I wouldn't mind it.
As for updated asynchronously, both setState and useState can batch updates. Although useState won't do batching when dealing with pending Promises, as opposed to ready-to-use values like count + 1 from your sample.
Coming back to setState, there is another reason why the flavor setState(oldState => ({...}) is better. Remember, setState always do merging no matter what. Combined with batching, it can lead to the following piece code executed by React if the flavor setState(newValue) was used:
Object.assign(oldState, newValue1, newValue2, newValue3)
where the values newValueX are the batched updates. Object.assign ensures that 'last update wins'. If the series of newValueX represents successive attempts to increment the same counter then it might not work as expected. Therefore the other flavor of setState is better. With useState and batching it looks like this kind of danger persists.

You can do something like following.
import React, { userState } from 'react';
function TestHook() {
const [count, setCount] = useState(0);
function updateCount() {
setCount(count + 1);
}
return <button onClick={handleCount}>{count}</button>
}
Check it here. https://codesandbox.io/s/one-with-the-hook-0yig1
You can follow it in my Medium post (https://blog.usejournal.com/getting-started-with-react-hooks-f0b5c1e3e0e7)

Related

React useState hook, calling setState with function

There is a concept in React (when using hooks) that confuses me.
I made a component for explanation (that increases a counter):
const [counter, setCounter] = useState(0); // counter hook
// code will follow
// render
return (
<div>
<button onClick={handleClick}>+</button>
<h3>{counter}</h3>
</div>
);
For the handler function, I have seen different options to set the state.
First method (using setState() normally):
const handleClick = () => {
setCounter(counter + 1);
};
Second method (creating a function inside setState() and returning the new value):
const handleClick = () => {
setCounter((counter) => {
return counter + 1;
});
};
I thought the difference would be that with the second method, you could immediately do a callback after setting the state, like this:
const handleClick = () => {
setCounter((counter) => {
return counter + 1;
}, () => {
console.log(counter); // trying callback after state is set
});
};
But when trying this (with both methods), the console displays the following error message:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
So I think in both cases, using useEffect() as a callback on setState() is the right way.
My question is: what is the difference between the two methods and what method is best to set the state. I have read about state immutability but can not immediately see how it would make a difference in this example.
In your case it's the same.
Basically when your state is computed with your previous state you can use the second approach which gets the previous value.
Have a look in React docs about this:
Functional updates
Since this question is gaining some attention I will add this example.
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount((prevCount) => prevCount - 1)}>-</button>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>+</button>
You can see that for + and - the functional setStateis being used, this is because the new state value is computed using the previous state (you are adding/subtracting from the previous count value).
The reset button is using the normal form, because it doesn't compute the new state value based on a computation on the old value, it always just sets it to a fixed number (for example 0).
So in my case, the best thing to do would have been using the functional setState.

Organizing and setting state from another file

I have some data (objects) that live in their own file (Origin.js).
This data is being exported using the spread operator within another object named OriginState:
Origin.js
//info
const info = {
title: '',
year: '',
};
//images
const images = {
logo: '',
heroImage: '',
};
//text
const text = {
header: '',
body: '',
};
export const OriginState = {
...info,
...images,
...text,
};
I am importing my OriginState object in another file and using it as state for my app like this:
OtherFile.js
import { OriginState } from './Origin.js';
const [state, setState] = useState({
...OriginState,
});
Here is an example handler where I am using this state to update some specified state values in an input later:
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Now, my question is... Is it incorrect to store state like this?
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
But I have a lot of state and didn't think it would be a good idea to have multiple setState hooks.
Is there a better way to do this? What I currently have just feels wrong.
Is it incorrect to store state like this?
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Nope, not at all, in fact, it is often the preferable pattern for state updates.
Any time your state update depends on the previous state, i.e. the classic counter example, or in your case, when there is nested state, you should use a functional state update to update from the previous state instead of the state from the previous render cycle.
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is
obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
I see no issue with your state update logic in the handler. In this count example it would (should) be considered incorrect to update a count like this. See this codesandbox demo that attempts to show the issue between non-functional and functional state updates.
The correct state update should be setCount(count => count + 1)
But I have a lot of state and didn't think it would be a good idea to
have multiple setState hooks.
Is there a better way to do this? What I currently have just feels
wrong.
When it comes to form inputs and state I think it makes sense to have a single flat object. There isn't really a right or wrong answer in general though when it comes to using a single useState hook with "complex" state shape, or to use a single useState hook for each "chunk" of state. It's an opinionated answer, mostly do what makes sense for a specific use-case.
Generally though I'd say if a set of values are even loosely related then perhaps it makes sense to store them in a common object, but this is my opinion.
A potential issue I see with your imported data though is the chance that you may inadvertently overwrite some key-value pairs by the use of the Spread syntax.
export const OriginState = {
...info,
...images, // <-- could overwrite values from info
...text, // <-- could overwrite values from info and images
};
Your approach seems quite legit, and this is one of the best practices that if your newState is depend on the oldState use setState callback and get the old state from callback input because otherwise as you showed above if you use it like this:
const [count, setCount] = useState(0);
setCount(count + 1)
you may increase the chance to get stale data which will increase the potential for bug

Asynchronous way to get the latest change in State with React Hooks

I have started learning React and developing an application using ReactJS. Recently i have moved to React Hooks. I know that in Class Component we can get the latest data from the state with the 2nd argument which is present in setState() like
state = {name: ''};
this.setState({name: name}, () => {console.log(this.state)});
I wanted to know if there is any arguments in React Hooks with which we can get the latest data from it.
I am using React Hooks in the below mentioned way, but upon console logging it always return the previous state
Hooks Eg:
const [socialData, setSocialData] = useState([
{ id: new Date().getTime().toString(), ...newItem }
]);
const onChangeCallback = (index, type, data) => {
let newSocialData = [...socialData];
newSocialData[index] = { ...newSocialData[index], [type]: data };
setSocialData(newSocialData);
onInputChange(newSocialData, formKey);
console.log(newSocialData);
};
The this.setState() second argument is not exactly to get the latest data from the state, but to run some code after the state has been effectively changed.
Remember that setting the state is an asynchronous operation. It is because React needs to wait for other potential state change requests so it can optimize changes and perform them in a single DOM update.
With this.setState() you can pass a function as the first argument, which will receive the latest known state value and should return the new state value.
this.setState((previousState) => {
const newState = previousState + 1;
return newState;
}, () => {
console.log('State has been updated!')
});
With that being said, there are special and rare cases when you need to know exactly when the state change has taken place. In many years of working with React I only faced this scenario once and I consider it a desperate attempt to make things work.
Usually, during a callback execution, like your onChangeCallback you want to change the state as the last thing your function does. If you already know the new state value, why do you want to wait for the real state to change to use it?
The new state value should be a problem to be handled during the next render.
If you want to run some code only when that particular state value changes you can do something like this:
import React, {useState, useEffect, useCallback} from 'react';
function MyComponent() {
const [value, setValue] = useState(false);
const onChangeHandler = useCallback((e) => {
setValue(!!e.target.checked);
}, []);
useEffect(() => {
// THIS WILL RUN ONLY WHEN value CHANGES. VERY SIMILAR TO WHAT YOU ARE TRYING TO DO WITH THE this.setState SECOND ARGUMENT.
}, [value]);
return (
<input type='checkbox' onChange={onChangeHandler} />
);
}
There is also a way to create a custom useState hook, to allow you passing a second argument to setValue and mimic the behavior of this.setState, but internally it would do exactly what I did in the above component. Please let me know if you have any doubt.

Understanding a basic implementation of Redux | how is prevState used?

I'm looking at this example of a super basic implementation of redux from this article. I understand it except where dispatch uses prevState. First off where does this function get prevState? How is this related to the actually state that counter needs? Does it implicitly set another value in the state called prevState? I'm just having a hard time understanding how the state actually gets passed to dispatch and then counter through prevState. I think this is probably a functional programming idea that I just haven't grasped yet. Thanks for helping me understand!
import React, { Component } from 'react';
const counter = (state = { value: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}
class Counter extends Component {
state = counter(undefined, {});
dispatch(action) {
this.setState(prevState => counter(prevState, action));
}
increment = () => {
this.dispatch({ type: 'INCREMENT' });
};
decrement = () => {
this.dispatch({ type: 'DECREMENT' });
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
React.Component.prototype.setState
Well react's setState method is responsible for prevState here – not redux, just to be clear.
Let's look at how setState can be used. We have 3 options ...
setState(nextState) - just pass the next state and React will update the components state accordingly
setState(nextState, callback) – optionally specify a callback that is called once the component is re-rendered – a React component will re-render whenever state is updated – Note: React docs generally recommend to use componentDidUpdate lifecycle method for this instead.
setState((prevState, props) => nextState) – call with a function that conveniently binds prevState and props identifiers for us to manually update our state. It's expected that the function we provide returns the next state
So you have this
this.setState(prevState => counter(prevState, action));
If it's not obvious, the code example you've provided is using #3. So, the first parameter prevState will be provided by React which is the current state of the component. The second parameter props is not used here, so we'll just ignore it (tho the general idea is you could use props to update your state if relevant).
What is prevState?
What exactly is prevState? Well we established that it's the current state of your component, which we initialized earlier in the Counter as
state = counter(undefined, {});
// => { value: 0 }
So when we dispatch an INCREMENT we will get something along the lines of
this.setState(prevState => counter({value: 0}, {type: 'INCREMENT'})
In which case counter (reducer) will return
{value: 1}
This return value is what will be the next state of the Component
Repeated applications of setState
Of course if we were to INCREMENT again, we'd have something like
this.setState(prevState => counter({value: 1}, {type: 'INCREMENT'})
Where counter would return
{value: 2}
which becomes the next state of the Component
and so on..
"Where is line between React and Redux?"
To start, the React-specific code is the import and the Counter class which extends Component.
"But what is the other code (counter) then?"
One of the coolest things about redux is its state of nothingness – Redux only exists in this code example as a pattern. There's no import which uses the redux or react-redux. Redux is used here more as an implementation of the idea/philosophy of redux – which is built around this idea of unidirectional flow of data and composable reducers.
"What is a reducer?"
A reducer is simply a function which, when applied to a state and an action, returns a new state.
Of course the redux library includes some helpful utilities which make it easier to implement Redux's patterns within your application – but really, they're all incredibly simple functions. In fact, the creator of Redux, Dan Abramov, has an awesome (FREE) series on egghead, Getting Started with Redux which shows you exactly how Redux works, piece by piece. In my opinion, it is one of the best coding video series ever created, on any topic.
prevState in this case is an actual, current state. It's not traveling back in time, it simply returns your current state, which is going to be used in order to build a new one - because in Redux and React's concept, the state is immutable, that means it never gets modified - when you dispatch new action and handle it with your reducer - you're creating completely new object (state).
First, note that the Class Counter extends from React's Component type. Because of this it will inherit a bunch of properties and methods, one of which is setState.
We can see from the React documentation for setState that it takes two arguments:
setState(nextState, callback)
But in the fine print it says this: "The first argument can be an object (containing zero or more keys to update) or a function (of state and props) that returns an object containing keys to update."
In the case here, we are only passing one argument, so we must be using it with the first argument being a function that returns an object of keys to update.
If we take another look at the original code where setState is used:
this.setState(prevState => counter(prevState, action));
It might be a little easier to read and understand if we wrote this in es5 JavaScript syntax:
this.setState(
function cb(prevstate) {
return counter(prevstate, action)
})
So in this case "prevState" is an argument to an anonymous function. In theory, it could be named anything and as long as you use the same name to refer to it inside on the function body everything will be fine. However, it seems like a pretty standard thing to name it "prevState" (that's what the React docs use).
If we look at this as purely JavaScript the prevState that you are passing into this function should be undefined.
Therefore, firing the INCREMENT action multiple times should result in your state value always being 1 since the counter function will always use the default value:
state = { value: 0 }
I would think the dispatch function should look like to this:
dispatch(action) {
this.setState(counter(this.state, action));
}
UPDATE
This link may explain it better, but indeed if you use a function as the first argument for setState then this function will take the current state as its argument, and so that is how prevState gets its value.

setState doesn't update the state immediately [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
The useState set method is not reflecting a change immediately
(15 answers)
Closed 8 months ago.
I would like to ask why my state is not changing when I do an onClick event. I've search a while ago that I need to bind the onClick function in constructor but still the state is not updating.
Here's my code:
import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';
import style from 'styles/boarditem.css';
class BoardAdd extends React.Component {
constructor(props) {
super(props);
this.state = {
boardAddModalShow: false
};
this.openAddBoardModal = this.openAddBoardModal.bind(this);
}
openAddBoardModal() {
this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true
/* After setting a new state it still returns a false value */
console.log(this.state.boardAddModalShow);
}
render() {
return (
<Col lg={3}>
<a href="javascript:;"
className={style.boardItemAdd}
onClick={this.openAddBoardModal}>
<div className={[style.boardItemContainer,
style.boardItemGray].join(' ')}>
Create New Board
</div>
</a>
</Col>
);
}
}
export default BoardAdd
Your state needs some time to mutate, and since console.log(this.state.boardAddModalShow) executes before the state mutates, you get the previous value as output. So you need to write the console in the callback to the setState function
openAddBoardModal() {
this.setState({ boardAddModalShow: true }, function () {
console.log(this.state.boardAddModalShow);
});
}
setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.
According to React docs
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Why would they make setState async
This is because setState alters the state and causes rerendering. This
can be an expensive operation and making it synchronous might leave
the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better
UI experience and performance.
Fortunately setState() takes a callback. And this is where we get updated state.
Consider this example.
this.setState({ name: "myname" }, () => {
//callback
console.log(this.state.name) // myname
});
So When callback fires, this.state is the updated state.
You can get mutated/updated data in callback.
For anyone trying to do this with hooks, you need useEffect.
function App() {
const [x, setX] = useState(5)
const [y, setY] = useState(15)
console.log("Element is rendered:", x, y)
// setting y does not trigger the effect
// the second argument is an array of dependencies
useEffect(() => console.log("re-render because x changed:", x), [x])
function handleXClick() {
console.log("x before setting:", x)
setX(10)
console.log("x in *line* after setting:", x)
}
return <>
<div> x is {x}. </div>
<button onClick={handleXClick}> set x to 10</button>
<div> y is {y}. </div>
<button onClick={() => setY(20)}> set y to 20</button>
</>
}
Output:
Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20
Live version
Since setSatate is a asynchronous function so you need to console the state as a callback like this.
openAddBoardModal(){
this.setState({ boardAddModalShow: true }, () => {
console.log(this.state.boardAddModalShow)
});
}
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. For instance, suppose we wanted to increment a value in state by props.step:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Think of setState() as a request rather than an immediate command to
update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
Check this for more information.
In your case you have sent a request to update the state. It takes time for React to respond. If you try to immediately console.log the state, you will get the old value.
The above solutions don't work for useState hooks.
One can use the below code
setState((prevState) => {
console.log(boardAddModalShow)
// call functions
// fetch state using prevState and update
return { ...prevState, boardAddModalShow: true }
});
This callback is really messy. Just use async await instead:
async openAddBoardModal(){
await this.setState({ boardAddModalShow: true });
console.log(this.state.boardAddModalShow);
}
If you want to track the state is updating or not then the another way of doing the same thing is
_stateUpdated(){
console.log(this.state. boardAddModalShow);
}
openAddBoardModal(){
this.setState(
{boardAddModalShow: true},
this._stateUpdated.bind(this)
);
}
This way you can call the method "_stateUpdated" every time you try to update the state for debugging.
Although there are many good answers, if someone lands on this page searching for alternative to useState for implementing UI components like Navigation drawers which should be opened or closed based on user input, this answer would be helpful.
Though useState seems handy approach, the state is not set immediately and thus, your website or app looks laggy... And if your page is large enough, react is going to take long time to compute what all should be updated upon state change...
My suggestion is to use refs and directly manipulate the DOM when you want UI to change immediately in response to user action.
Using state for this purspose is really a bad idea in case of react.
setState() is asynchronous. The best way to verify if the state is updating would be in the componentDidUpdate() and not to put a console.log(this.state.boardAddModalShow) after this.setState({ boardAddModalShow: true }) .
according to React Docs
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately
According to React Docs
React does not guarantee that the state changes are applied immediately.
This makes reading this.state right after calling setState() a potential pitfall and can potentially return the existing value due to async nature .
Instead, use componentDidUpdate or a setState callback that is executed right after setState operation is successful.Generally we recommend using componentDidUpdate() for such logic instead.
Example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 1
};
}
componentDidUpdate() {
console.log("componentDidUpdate fired");
console.log("STATE", this.state);
}
updateState = () => {
this.setState(
(state, props) => {
return { counter: state.counter + 1 };
});
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.updateState}>Update State</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
this.setState({
isMonthFee: !this.state.isMonthFee,
}, () => {
console.log(this.state.isMonthFee);
})
when i was running the code and checking my output at console it showing the that it is undefined.
After i search around and find something that worked for me.
componentDidUpdate(){}
I added this method in my code after constructor().
check out the life cycle of react native workflow.
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
Yes because setState is an asynchronous function. The best way to set state right after you write set state is by using Object.assign like this:
For eg you want to set a property isValid to true, do it like this
Object.assign(this.state, { isValid: true })
You can access updated state just after writing this line.

Categories

Resources