React setState + Where does 'prevState' come from? - javascript

I just started learning React and JavaScript.
While going through the tutorial, I got to this example code of a component, which creates a toggle button.
This is part of the code:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({ // prevState?
isToggleOn: !prevState.isToggleOn
}));
}
2 things that are bugging me here:
Where did the prevState argument come from?
I don't see anything like var prevState = this.state; before calling it, and still, it works.
The syntax of the arrow function: why the parentheses after the arrow?
Why doesn't the usual arg => { statement; } syntax work here?
Sorry for the newbie questions...

prevState is provided by React along with props, both of which are optional.
Update 04/13/19: React has changed the setState function documentation by renaming prevState to updater. The callback function still takes two arguments; the state and props at the time the change is being applied.
The parenthesis allow multiple lines where if you didn't use the parenthesis you'd be forced to used a return. You could use a single line but you don't need the curly braces.
Update: I forgot to mention a specific case where it is required to have parenthesis. If you're returning an object without a return statement you must wrap it in parenthesis. Thank you #joedotnot for catching that. So () => {foo: true} will throw an error because it looks like a function and foo: true is an invalid line. To fix this it must look like () => ({ foo: true })

Im use this. (Example)
const [modal, setModal] = useState(false);
const [dataAction, setDataAction] = useState({name: '', description: ''});
const _handleChangeName = (data) => {
if(data.name)
setDataAction( prevState => ({ ...prevState, name : data.name }));
if(data.description)
setDataAction( prevState => ({ ...prevState, description : data.description }));
};

Related

React state does not update immediately [duplicate]

I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Calling setState() in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState() into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called every time myArr has changed
// perform some action every time myArr is updated
console.log('Updated State', myArr)
}, [myArr])
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState instead of the object.
So setState((state, props) => {...}) instead of setState(object).
The reason is that setState is more of a request for the state to change rather than an immediate change. React batches those setState calls for performance improvement.
Meaning the state property you're checking might not be stable.
This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState() that accepts a function rather than an object:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
As mentioned above setState() is asynchronous in nature. I solved this issue simply using async await.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Here is React Hooks based solution.
Since React useState updates state asynchronously, check them in the useEffect hook if you need to see these changes.
Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.
1) let errorsArray = [];
2) let [errors, setErrors] = useState(errorsArray);
3) let [firstName, setFirstName] = useState('');
4) let [lastName, setLastName] = useState('');
let [gender, setGender] = useState('');
let [email, setEmail] = useState('');
let [password, setPassword] = useState('');
const performRegister = () => {
console.log('firstName', isEmpty(firstName));
if (isEmpty(firstName)) {
console.log('first if statement');
errorsArray.push({firstName: 'First Name Cannot be empty'});
}
if (isEmpty(lastName)) {
errorsArray.push({lastName: 'Last Name Cannot be empty'});
}
if (isEmpty(gender)) {
errorsArray.push({gender: 'Gender Cannot be empty'});
}
if (isEmpty(email)) {
errorsArray.push({email: 'Email Cannot be empty'});
}
if (isEmpty(password)) {
errorsArray.push({password: 'Password Cannot be empty'});
}
console.log('outside ERRORS array :::', errorsArray);
setErrors(errorsArray);
console.log('outside ERRORS :::', errors);
if (errors.length > 0) {
console.log('ERROR exists');
}
};

React use setState to update object but not re-render

I want to use setState to update the object in state which called 'all_cart",
but not matter which method I tryed, it cannot trigger re-render, I know React will use === to check two object in state is changed or not.
Here is my code:
this.setState({
all_cart: {
...this.state.all_cart,
cart_data : _response['data'].data.cart_data
}
})
this.setState(({all_cart}) => ({
all_cart: {
...this.state.all_cart,
cart_data : _response['data'].data.cart_data
}
}))
How can I do?
According to the React docs if you have props, it is wrong approach:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
And your code would look like this:
this.setState((state, props) => ({
all_cart: {
...state.all_cart,
cart_data : _response['data'].data.cart_data
}
}))
UPDATE:
However, if you do not use props, then it looks like your function is not is scope, so try to use arrow function. Let me show an example:
handleClick = () => {
this.setState(this.someApiResult())
}
So a complete stackblitz example can be seen here.
As per my understanding, you want to merge _response['data'].data.cart_data with the existing state which is all_cart,
in this case, you should try this
function updateCart(){
const updatedData = {...this.state.all_cart, ..._response['data'].data.cart_data};
this.setState({all_cart: updatedData});
}

Why my setstate gives me undefined? React [duplicate]

I'm working on a todo application. This is a very simplified version of the offending code. I have a checkbox:
<p><input type="checkbox" name="area" checked={this.state.Pencil} onChange={this.checkPencil}/> Writing Item </p>
Here's the function that calls the checkbox:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
});
this.props.updateItem(this.state);
}
updateItem is a function that's mapped to dispatch to redux
function mapDispatchToProps(dispatch){
return bindActionCreators({ updateItem}, dispatch);
}
My problem is that when I call the updateItem action and console.log the state, it is always 1 step behind. If the checkbox is unchecked and not true, I still get the state of true being passed to the updateItem function. Do I need to call another function to force the state to update?
You should invoke your second function as a callback to setState, as setState happens asynchronously. Something like:
this.setState({pencil:!this.state.pencil}, myFunction)
However in your case since you want that function called with a parameter you're going to have to get a bit more creative, and perhaps create your own function that calls the function in the props:
myFunction = () => {
this.props.updateItem(this.state)
}
Combine those together and it should work.
Calling setState() in React is asynchronous, for various reasons (mainly performance). Under the covers React will batch multiple calls to setState() into a single state mutation, and then re-render the component a single time, rather than re-rendering for every state change.
Fortunately, the solution is rather simple - setState accepts a callback parameter:
checkPencil: () => {
this.setState(previousState => ({
pencil: !previousState.pencil,
}), () => {
this.props.updateItem(this.state);
});
}
On Ben Hare's answer, If someone wants to achieve the same using React Hooks I have added sample code below.
import React, { useState, useEffect } from "react"
let [myArr, setMyArr] = useState([1, 2, 3, 4]) // the state on update of which we want to call some function
const someAction = () => {
let arr = [...myArr]
arr.push(5) // perform State update
setMyArr(arr) // set new state
}
useEffect(() => { // this hook will get called every time myArr has changed
// perform some action every time myArr is updated
console.log('Updated State', myArr)
}, [myArr])
When you're updating your state using a property of the current state, React documentation advise you to use the function call version of setState instead of the object.
So setState((state, props) => {...}) instead of setState(object).
The reason is that setState is more of a request for the state to change rather than an immediate change. React batches those setState calls for performance improvement.
Meaning the state property you're checking might not be stable.
This is a potential pitfall to be aware of.
For more info see documentation here: https://facebook.github.io/react/docs/react-component.html#setstate
To answer your question, i'd do this.
checkPencil(){
this.setState((prevState) => {
return {
pencil: !prevState.pencil
};
}, () => {
this.props.updateItem(this.state)
});
}
It's because it happens asynchronously, so means in that time might not get updated yet...
According to React v.16 documentation, you need to use a second form of setState() that accepts a function rather than an object:
State Updates May Be Asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
For example, this code may fail to update the counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
First set your value. after proceed your works.
this.setState({inputvalue: e.target.value}, function () {
this._handleSubmit();
});
_handleSubmit() {
console.log(this.state.inputvalue);
//Do your action
}
I used both rossipedia's and Ben Hare's suggestions and did the following:
checkPencil(){
this.setState({
pencil:!this.state.pencil,
}, this.updatingItem);
}
updatingItem(){
this.props.updateItem(this.state)
}
Ben has a great answer for how to solve the immediate issue, however I would also advise to avoid duplicating state
If a state is in redux, your checkbox should be reading its own state from a prop or store instead of keeping track of the check state in both its own component and the global store
Do something like this:
<p>
<input
type="checkbox"
name="area" checked={this.props.isChecked}
onChange={this.props.onChange}
/>
Writing Item
</p>
The general rule is that if you find a state being needed in multiple places, hoist it up to a common parent (not always redux) to maintain only having a single source of truth
try this
this.setState({inputvalue: e.target.value}, function () {
console.log(this.state.inputvalue);
this.showInputError(inputs[0].name);
});
showInputError function for validation if using any forms
As mentioned above setState() is asynchronous in nature. I solved this issue simply using async await.
Here's an example for refernce:
continue = async (e) => {
e.preventDefault();
const { values } = this.props;
await this.setState({
errors: {}
});
const emailValidationRegex = /^(([^<>()\[\]\.,;:\s#\"]+(\.[^<>()\[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i;
if(!emailValidationRegex.test(values.email)){
await this.setState((state) => ({
errors: {
...state.errors,
email: "enter a valid email"
}
}));
}
}
You can also update the state twice like below and make the state update immediately, this worked for me:
this.setState(
({ app_id }) => ({
app_id: 2
}), () => {
this.setState(({ app_id }) => ({
app_id: 2
}))
} )
Here is React Hooks based solution.
Since React useState updates state asynchronously, check them in the useEffect hook if you need to see these changes.
Make sure to give the initialState in the useState each time using a variable. Like line 1 and 2. If I did not give anything in it it would work on double click to fill the errors variable.
1) let errorsArray = [];
2) let [errors, setErrors] = useState(errorsArray);
3) let [firstName, setFirstName] = useState('');
4) let [lastName, setLastName] = useState('');
let [gender, setGender] = useState('');
let [email, setEmail] = useState('');
let [password, setPassword] = useState('');
const performRegister = () => {
console.log('firstName', isEmpty(firstName));
if (isEmpty(firstName)) {
console.log('first if statement');
errorsArray.push({firstName: 'First Name Cannot be empty'});
}
if (isEmpty(lastName)) {
errorsArray.push({lastName: 'Last Name Cannot be empty'});
}
if (isEmpty(gender)) {
errorsArray.push({gender: 'Gender Cannot be empty'});
}
if (isEmpty(email)) {
errorsArray.push({email: 'Email Cannot be empty'});
}
if (isEmpty(password)) {
errorsArray.push({password: 'Password Cannot be empty'});
}
console.log('outside ERRORS array :::', errorsArray);
setErrors(errorsArray);
console.log('outside ERRORS :::', errors);
if (errors.length > 0) {
console.log('ERROR exists');
}
};

Passing property to component using component returned by HOC causes error

I have a DataComponent component and it is a HOC:
const DataComponent = (ComposedComponent, url) =>
class DataComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
loading: false
};
}
componentWillMount() {
this.setState({loading: true});
fetch(url)
.then(response => response.json())
.then(data => this.setState({
data,
loading: false,
loaded: true
}));
}
render() {
return(
<div className="data-component">
{(this.state.loading) ?
<div>Loading</div> :
<ComposedComponent {...this.state}/>}
</div>
);
}
}
Then I use RandomMeUsers to render that HOC (PeopleList is another component):
const RandomMeUsers = DataComponent(PeopleList, `https://randomuser.me/api/?results=10`);
ReactDOM.render(<RandomMeUsers/>, document.getElementById("app"));
It works fine, then I pass count property to RandomMeUsers like this:
const RandomMeUsers = ({count}) => DataComponent(PeopleList, `https://randomuser.me/api/?results=${count}`);
ReactDOM.render(<RandomMeUsers count={10}/>, document.getElementById("app"));
When I run it, browser sends me this error:
Warning: Functions are not valid as a React child.
This may happen if you return a Component instead of from render.
Or maybe you meant to call this function rather than return it.
in RandomMeUsers
What problems does my code have?
#Treyco explained well why you are getting this error. As an alternative to their answer maybe you can use count and url like this.
const RandomMeUsers = DataComponent(
PeopleList,
"https://randomuser.me/api/?results="
);
and inside your HOC:
fetch(`${url}${this.props.count}`)
.then(response => response.json())
....
But this logic will be less useful if your url needs more parameter in the future. So, instead of passing url as an argument to your HOC, maybe you can extract it and place it into your props logic. In this way, you can manipulate your url somewhere else and pass it as a prop.
You converted the result of your HOC into an arrow function. This function will not replace your component's behavior and pass props.
An ugly syntax would be the following :
const RandomMeUsers = ({ count }) => DataComponent(PeopleList, `https://randomuser.me/api/?results=${count}`);
const RandomTenUsers = RandomMeUsers({ count: 10 })
ReactDOM.render(<RandomTenUsers />, document.getElementById("app"));
And maybe this syntax is correct :
ReactDOM.render(RandomMeUsers({ count: 10 }), document.getElementById("app"));

React setState re-render

First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys

Categories

Resources