In the React environment, working with properties and states is an obvious thing, because both props and state represent rendered values, i.e., what’s currently seen on the screen.
To apply changes to a component we need to change the components state or props. React only updates what’s necessary by comparing an element and its children to the previous one, and applies the DOM updates to bring the DOM to the desired state.
Like React documentation suggests:
Once you create an element, you can’t change its children or attributes. An element is like a single frame in a movie: it represents the UI at a certain point in time.
The most important thing about the state is that we can’t rely on it to reflect the new value immediately after calling setState.
Let’s consider a very simple example!
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
import "./style.css";
const Subscribe = () => {
const [inputValue, setInputValue] = useState("");
const [submitValue, setSubmitValue] = useState("");
const [isEmail, setIsEmail] = useState(false);
const handleSubmit = e => {
e.preventDefault();
console.log("Subscribed");
if (isEmail) {
setSubmitValue(inputValue);
} else {
setIsEmail(false);
}
};
const handleChange = e => {
console.log("on input change value");
const currentValue = e.target.value;
setInputValue(currentValue);
setSubmitValue(inputValue);
const isEmailValid = currentValue.includes("@");
if (isEmailValid) {
console.log("includes");
setIsEmail(true);
} else {
setIsEmail(false);
}
};
return (
<div className="container">
<h2>Subscribe Functional Componenet</h2>
<form onSubmit={handleSubmit} className="subscribe">
<label>
Email:
<input
onChange={handleChange}
value={inputValue}
type="email"
className={`subscribe__email ${isEmail ? "success" : "error"}`}
/>
</label>
<input
type="submit"
value="Submit"
aria-label="submit button"
className={`subscribe__submit ${isEmail ? "" : "not-allowed"}`}
disabled={isEmail ? false : true}
/>
</form>
<p>
Input value: {inputValue} <br />
Input value length: {inputValue.length}
</p>
<p>
Submit value: {submitValue} <br />
Submit value length: {submitValue.length}
</p>
<p className={`${isEmail ? "success" : "error"}`}>
{`Email correct: ${isEmail}`}
</p>
</div>
);
};
const App = () => {
return (
<div>
<h1>Why is STATE giving me the wrong value?</h1>
<Subscribe />
</div>
);
};
render(<App />, document.getElementById("root"));
CSS
* {
box-sizing: border-box;
}
h1, p {
font-family: Lato;
}
.container {
margin: 50px 0;
}
.subscribe {
background: #F2F1F1;
padding: 50px;
}
.subscribe__email {
padding: 5px;
margin: 0 10px;
}
.subscribe__submit {
padding: 5px;
}
.subscribe__submit:hover {
cursor: pointer;
}
.subscribe__submit.not-allowed:hover {
cursor: not-allowed;
}
.error {
color: red;
}
.success {
color: green;
}
What is going on here?
We have a stateful Subscribe Component with two simple events:
- handleSubmit — sets the input value from the input field and displays it on UI
- handleChange — reads text from the input field and stores it in inputValue and in the same place sets the submitValue which is incorrect and will cause a problem reflected on UI.
Under our simple form, we have two sections with live date from input field. The first one shows what user types in the input field. The second one indicates what will be sent from our form.
Right now our code is broken.
Once we start writing, you can see that inputValue is different than submitValue. That is exactly our case! We are setting state before it was changed. To fix that we need to:
set our submitValue in handleChange function to be equal to the currentValue
Without that, even if we deleted all characters from the input field our state will be behind. In addition, our submit method does nothing else than login ’Subscribed’ in the browser console and setting state for submitValue.
const handleChange = e => {
console.log("on input change value");
const currentValue = e.target.value;
submitValue = currentValue;
setInputValue(currentValue);
setSubmitValue(currentValue);
const isEmailValid = currentValue.includes("@");
if (isEmailValid) {
console.log("includes");
setIsEmail(true);
} else {
setIsEmail(false);
}
};
One extra thing that I've added is to clear submitValue when inputValue is an empty string. You can see it at the end of handleChange function
Finally, we have set up everything properly.
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
import "./style.css";
const Subscribe = () => {
const [inputValue, setInputValue] = useState("");
const [submitValue, setSubmitValue] = useState("");
const [isEmail, setIsEmail] = useState(false);
const handleSubmit = e => {
e.preventDefault();
console.log("Subscribed", submitValue);
if (isEmail) {
setSubmitValue(inputValue);
} else {
setIsEmail(false);
}
};
const handleChange = e => {
console.log("on input change value");
const currentValue = e.target.value;
submitValue = currentValue;
setInputValue(currentValue);
setSubmitValue(currentValue);
const isEmailValid = currentValue.includes("@");
if (isEmailValid) {
console.log("includes");
setIsEmail(true);
} else {
setIsEmail(false);
}
if (!currentValue) {
setSubmitValue("");
}
};
return (
<div className="container">
<h2>Subscribe Functional Componenet</h2>
<form onSubmit={handleSubmit} className="subscribe">
<label>
Email:
<input
onChange={handleChange}
value={inputValue}
type="email"
className={`subscribe__email ${isEmail ? "success" : "error"}`}
/>
</label>
<input
type="submit"
value="Submit"
aria-label="submit button"
className={`subscribe__submit ${isEmail ? "" : "not-allowed"}`}
disabled={isEmail ? false : true}
/>
</form>
<p>
Input value: {inputValue} <br />
Input value length: {inputValue.length}
</p>
<p>
Submit value: {submitValue} <br />
Submit value length: {submitValue.length}
</p>
<p className={`${isEmail ? "success" : "error"}`}>
{`Email correct: ${isEmail}`}
</p>
</div>
);
};
const App = () => {
return (
<div>
<h1>Why is STATE giving me the wrong value?</h1>
<Subscribe />
</div>
);
};
render(<App />, document.getElementById("root"));
Summary
The summary for this article is very short and we can close it in one sentence:
remember to not rely on the state to reflect the new value immediately after calling setState.
If you have more doubts or something is still not clear, follow this link from React documentation: https://reactjs.org/docs/faq-state.html
You can also visit this link to check the code for this example.