The almighty useState hook — React.js

Jasmine Jiang
Women in Technology
4 min readJun 20, 2023

Looking back, my first week of Flatiron bootcamp phase 2 would be best summed up as “sustained and unabated befuddlement.” But now on the other side of it, I can safely say I’ve gained a decent understanding of React. It did not come as easily as JavaScript did, but I’d been prepared for a ramp-up in the challenge factor anyway!

What is a hook?

Hooks are specific to React, and were also the elements we used most often throughout Flatiron phase 2’s labs/activities. They are a special kind of function that “hook” into React’s internal state inside a component, and isolate a chosen variable from the life cycle of its container.

What is the useState hook?

A “state” is dynamic data that is expected to change over time, and the useState hook helps us to declare a variable as such while also providing a method to set its value upon user interaction/input.

//always remember to import the hook!
import React, { useState } from React;

function App() {
const [input, setInput] = useState("");

return (
<>
<h1>{input}</h1>
<input type="text" onChange={(e) => setInput(e.target.value)}></input>
</>
)
}

Lots going on here! Let’s break it down.

import React, { useState } from React;

To actually use the useState hook (or any other hook) you need to import it into your file.

const [input, setInput] = useState("");

This statement establishes a state variable named “input”, the name of the method that will be used to change its value, setInput(), and initializes the variable with a value inside the parenthesis of useState. In this case, the value is an empty string.

Therefore when this component is loaded, the value of the variable “input” starts out as empty.

return (
<>
<h1>{input}</h1>
<input type="text" onChange={(e) => setInput(e.target.value)}></input>
</>
)

In the JSX portion of the component, the content of the <h1> element is the value of “input”, which is currently empty and so will not display. But if one types into the <input> element, the setInput() method will be called to update the value of the variable in real time and display it on the DOM!

This is possible due to an inherent property of a state variable: with every change in its value, it will re-render onto the DOM. The page does not need to reload to display the changes in the state variable, which makes for a more streamlined and responsive user experience.

Considerations

One aspect of the useState hook I personally grappled with was its asynchronous nature, which brought me up short on at least one occasion as I worked through our lab exercises. Because a setState function is removed from the regular flow of code execution, this code example would not work as intended:

import React, { useState } from React;

function App() {
const [input, setInput] = useState("");

function handleTextChange(e) {
setInput(e.target.value);
console.log(input);
}

return (
<>
<h1>{input}</h1>
<input type="text" onChange={handleTextChange}></input>
</>
)
}

While the “input” variable would still display properly on the DOM, the console.log(input) would not log an accurate value; rather, it would always be one state behind. You cannot set state and use that set value within the same code block or function, as we must wait for the next render cycle before we get the most recent value.

What is useState good for?

As a rule of thumb, useState should be applied to any variable whose change in value is integral to the React app’s functionality.

Examples:

function Counter() {
//a counter variable that displays a new number for every click
const [counter, setCounter] = useState(0);

function handleClick() {
setCounter(counter + 1);
}

return (
<button onClick={handleClick}>{counter}</button>
)
}

A more complex application of state is controlled inputs for forms:

function Form() {

const [input, setInput] = useState("");

return (
<form>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}></input>
<input value="submit" type="submit"></input>
</form>
)
}

By tethering the state variable and the value property to each other, this creates a controlled value that can be used in validation and conditional logic that is responsive to the user.

function Form() {

const [input, setInput] = useState("");

function handleSubmit(e) {
e.preventDefault();
if (input === "") {
return;
} else {
console.log(input);
}
}

return (
<form onSubmit={handleSubmit}>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}></input>
<input value="submit" type="submit"></input>
</form>
)
}

The added handleSubmit() function implements validation logic with a conditional; if the “input” value is empty, then nothing visibly happens when the form is submitted. But if the “input” value is not empty, the console will log the variable’s value.

Why not get fancy and add helpful messages that appear depending on the validity of the form?

function Form() {

const [input, setInput] = useState("");
const [formValid, setFormValid] = useState(null);

function handleSubmit(e) {
e.preventDefault();
if (input === "") {
setFormValid(false);
return;
} else {
setFormValid(true);
console.log(input);
}
}

return (
<form onSubmit={handleSubmit}>
{/*ternary to determine content of <p>*/}
<p>{formValid ? "Success!" : "Please enter a valid input." }</p>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)}></input>
<input value="submit" type="submit"></input>
</form>
)
}

The message contents will change depending on the value of the “formValid” variable, and will automatically update upon state change without requiring a reload. And along with this final example emerges another use for state variables: toggling visibility of elements on the DOM!

My own ample use of state (and many other React features) are demonstrated in my phase 2 final project, a book collection app with its own hosted JSON backend. Check it out here!

--

--