6.state in React
6.state in React
• class-based component
• functional component
We'll start with class-based components now. Later in this
article, we will see a functional component way of creating
components.
You should know how to work with class-based components
as well as functional components, including hooks.
this.state = {
counter: 0
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.state.counter = this.state.counter + 1;
console.log("counter", this.state.counter);
}
render() {
const { counter } = this.state;
return (
<div>
<button onClick={this.handleClick}>Increment
counter</button>
<div>Counter value is {counter}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Counter />, rootElement);
handleClick() {
this.setState((prevState) => {
return {
counter: prevState.counter + 1
};
});
console.log("counter", this.state.counter);
}
Here, we're passing a function as a first argument to
the setState function and we're returning a new state object
with counter incremented by 1 based on the previous value
of counter.
We're using the arrow function in the above code, but using
a normal function will also work.
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.handleClick = this.handleClick.bind(this);
}
To use the this keyword inside the handleClick event handler,
we have to bind it in the constructor like this:
this.handleClick = this.handleClick.bind(this);
Also, to declare the state, we have to create a constructor,
add a super call inside it, and then we can declare the state.
This is not just cumbersome but also makes the code
unnecessarily complicated.
state = {
counter: 0
};
and the handlerClick event handler is changed to arrow
function syntax like this:
handleClick = () => {
this.setState((prevState) => {
return {
counter: prevState.counter + 1
};
});
};
As arrow functions do not have their own this context, it will
take the context as the class so there is no need to use
the .bind method.
This makes the code a lot simpler and easier to understand as
we don't need to keep binding every event handler.
render() {
const { name } = this.state;
return (
<div>
<input
type="text"
onChange={this.handleChange}
placeholder="Enter your name"
value={name}
/>
<div>Hello, {name}</div>
</div>
);
}
}
Here, we've added an input textbox where the user types
their name and it's displayed below the textbox as the user
types into the textbox.
In the state, we've initialized the name property to Mike and
we've added an onChange handler to the input textbox like
this:
state = {
name: "Mike"
};
...
<input
type="text"
onChange={this.handleChange}
placeholder="Enter your name"
value={name}
/>
So when we type anything in the textbox, we're updating the
state with the value typed by passing an object to
the setState function.
handleChange = (event) => {
const value = event.target.value;
this.setState({ name: value });
}
But which form of setState should we use – what's
preferred? We have to decide whether to pass an object or a
function as a first argument to the setState function.
The answer is: pass an object if you don't need
the prevState parameter to find the next state value.
Otherwise pass the function as the first argument to setState.
But you need to be aware of one issue with passing an object
as an argument.
this.setState({
counter: counter + 1
});
}
Here, we're first setting the counter value to 5 and then
incrementing it by 1. So the expected value of counter is 6.
Let's see if that's the case.
As you can see, when we click the button the first time, we
expected the counter value to become 5 – but it becomes 1,
and on every subsequent click it's incremented by 1.
This is because, as we have seen previously,
the setState function is asynchronous in nature. When we
call setState, the value of the counter does not become 5
immediately, so on the next line we're getting
the counter value of 0 to which we've initialized the state in
the beginning.
So it becomes 1 when we call setState again to increment
the counter by 1, and it keeps on incrementing by 1 only.
To fix this issue, we need to use the updater syntax
of setState where we pass a function as the first argument.
this.setState((prevState) => {
return {
counter: prevState.counter + 1
};
});
this.setState((prevState) => {
return {
counter: prevState.counter + 1
};
});
}
As you can see, when we first click on the button, the value
of counter becomes 7. This is as expected, because first we
set it to 5 and then incremented it by 1 twice so it becomes
7. And it remains at 7 even if we click the button multiple
times, because on every click we're re-setting it to 5 and
incrementing twice.
This is because inside the handleClick we're
calling setState to set the counter value to 5 by passing an
object as the first argument to the setState function. After
that, we've called two setState calls where we're using the
function as the first argument.
So how does this work correctly?
...
doSomethingElse = () => {
const { isLoggedIn } = this.state;
if(isLoggedIn) {
// do something different
}
};
handleClick = () => {
// some code
this.setState({ isLoggedIn: true);
doSomethingElse();
}
In the above code, we've defined an isLoggedIn state and we
have two functions handleClick and doSomethingElse. Inside
the handleClick function, we're updating the isLoggedIn state
value to true and immediately we're calling
the doSomethingElse function on the next line.
So inside doSomethingElse you might think that you're going
to get the isLoggedIn state as true and the code inside the if
condition will be executed. But it will not be executed
because setState is asynchronous and the state might not be
updated immediately.
That's why React added lifecycle methods
like componendDidUpdate to do something when state or
props are updated.
Keep an eye out to check if you're using the
same state variable again in the next line or next function to
do some operation to avoid these undesired results.
How to Merge setState Calls in React
this.setState((prevState) => ({
counter: prevState.counter + 1,
username: "somevalue"
}));
We can update only the one which we want to update. React
will manually merge the other state properties so we don't
need to worry about manually merging them ourselves.
return (
<div>
<p>
Name: {first} {last}
</p>
<p>Email: {email} </p>
<hr />
</div>
);
};
This User component is a functional component.
A functional component is a function that starts with a capital
letter and returns JSX.
handleOnClick = () => {
this.setState(prevState => ({
counter: prevState.counter + 1
}));
};
render() {
return (
<div>
<p>Counter value is: {this.state.counter} </p>
<button
onClick={this.handleOnClick}>Increment</button>
</div>
);
}
}
ReactDOM.render(<App />,
document.getElementById('root'));
Here's a Code Sandbox Demo which is written using class
components.
Let’s convert the above code to use hooks.
return (
<div>
<div>
<p>Counter value is: {counter} </p>
<button onClick={() => setCounter(counter +
1)}>Increment</button>
</div>
</div>
);
};
ReactDOM.render(<App />,
document.getElementById("root"));
As you can see, using React hooks makes the code a lot
shorter and easier to understand.