React Hooks for Beginners: Managing State

·
6 minute read
Feature image: React Hooks for Beginners: Managing State

We’ve been using React at Tighten for almost as long as we’ve been using Vue. In fact, one of our most-read blog post series is an extensive React tutorial by former Tightenite Samantha Geitz.

If you follow anyone in the React community, you’ve likely seen dozens of articles about React features like “hooks” and “contexts”. However, many of these articles focus on teaching them to seasoned React developers. That’s not the goal here.

This post is designed to be a very basic primer on React components and using hooks for state management for those unfamiliar with React. If you’re a Vue developer, or if this is your first experience with component-based JavaScript, it’s our hope that this post will demystify a bit of what’s going with these new React features.

Let’s build a component

Let’s get going! For this post, we’ll be building a simple user registration form. It will have three main pieces of logic:

  1. If the user’s password does not match the password confirmation, we’ll display an error.
  2. If the user’s password matches the password confirmation, we’ll show a success message.
  3. If we don’t have input in both password fields, we’ll display nothing.

The markup would look something like this:

<form>
<label for="email">Email</label>
<input type="text" name="email" />
 
<label for="password">Password</label>
<input type="password" name="password" />
 
<label for="confirm">Confirm Password</label>
<input type="password" name="confirm" />
 
<div>
<i class="material-icons">report</i>
Passwords do not match
</div>
 
<div>
<i class="material-icons">check_circle</i>
Passwords match
</div>
</form>

Now, let’s turn this HTML into a React Component.

First things first: every React component needs an element to render in. Let's create a file called index.html, and we'll add in a <div> with a unique id:

<div id="signup"></div>

Now, in our JavaScript file (let's call it index.js), we’ll include a little render statement:

ReactDOM.render(<SignUp />, document.getElementById('signup'));

This render() method tells ReactDOM (the library that React uses to interact with the DOM in our browser) to render a component called SignUp into the HTML element with the ID of signup.

At this point, we don’t actually have a component called SignUp, so we should see nothing rendered on the page. Let’s go ahead and create that component in a new file, SignUp.js.

function SignUp() {
return (
<form>
<label for="email">Email</label>
<input type="text" name="email" />
 
<label for="password">Password</label>
<input type="password" name="password" />
 
<label for="confirm">Confirm Password</label>
<input type="password" name="confirm" />
 
<div>
<i className="material-icons">report</i>
Passwords do not match
</div>
 
<div>
<i className="material-icons">check_circle</i>
Passwords match
</div>
</form>
);
}

Not bad so far. This is just a function returning HTML... well, almost. Technically this isn’t HTML, but something called JSX.

JSX is a templating system for JavaScript that is almost identical to HTML, save for a few exceptions.

If you’re paying extra-close attention you may already have seen one difference: we’ve replaced two usages of class with className. That’s because JSX is just JavaScript, and in JavaScript, class is a protected symbol (used to define classes). We’ve also adding closing slashes to the HTML inputs; these closing slashes aren’t necessary in HTML anymore, but they’re still required in JSX.

Another thing to note: you’re welcome to use ES6 arrow syntax when declaring functional components as well. So, instead of doing:

function SignUp() {
return (
...
);
}

you can do:

const SignUp = () => {
return (
...
);
}

At this point we should be successfully rendering some basic HTML, so let’s take a swing at adding state!

import {useState} from 'react';
 
function SignUp() {
const [password, setPassword] = useState("HackMe123!");
 
return (
...
<input type="password" name="password" value={password} />
...
);
}

OK, let’s pause for a second. What did we just do?

First, we imported the useState hook from React. Hooks are the way to access state and lifecycle events within a functional component. React has two types of components: class components (i.e. a component that extends the React.Component class) and functional components, which is the component syntax we're using in this tutorial.

Previously, state and lifecycle information could only be accessed from within a class component. That meant that if you needed to track the current value of a variable or access lifecycle events, you’d have to create a class component, which could lead to a lot of unneeded complexity. With hooks, we can access this information in a functional component.

One important thing to note here: hooks do not work inside of class components–they’re designed to be used in lieu of them.

For our component here, we’re using the useState hook to access and update the value of password.

Next, using JavaScript’s destructuring assignment, we assigned two new variables (password and setPassword) to the output of useState("Hackme123!"). We can do this because useState returns an array with two values: a value that we can track (password), and a function that we can use to update that value (setPassword). This is our first React Hook.

[variableName, setVariableName] is a standard naming convention for the useState hook.

You can see in our return JSX that we’re now passing password into our input. This means that, if you check your browser, you’ll see that the password field is now filled. It’ll be hidden because the input is type="password", but its value is Hackme123!.

Now let’s use our shiny new hook’s setPassword function to update the value of password as we type:

import {useState} from 'react';
 
function SignUp() {
const [password, setPassword] = useState("HackMe123!");
 
return (
...
<input type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
...
);
}

Now when we type into the password field, the value of password will be automatically updated. If you're familiar with Vue, this is the equivalent of data binding.

Let’s add another hook to add the same functionality to our confirm input.

import {useState} from 'react';
 
function SignUp() {
const [password, setPassword] = useState();
const [confirm, setConfirm] = useState();
 
return (
...
<input type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
...
<input type="password"
name="confirm"
value={confirm}
onChange={(event) => setConfirm(event.target.value)}
/>
...
);
}

We’ve also removed the “Hack123!” default; it was useful for demonstration purposes, but we wouldn’t actually want to use a default password for a real form.

Finally, we can now compare the values of password and confirm and use the results to conditionally render our messages:

import {useState} from 'react';
 
function SignUp() {
const [password, setPassword] = useState();
const [confirm, setConfirm] = useState();
 
const bothFieldsAreFilled = password && confirm;
const passwordsMatch = password === confirm;
 
return (
<form>
<label for="email">Email</label>
<input type="text" name="email" />
 
<label for="password">Password</label>
<input type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
 
<label for="confirm">Confirm Password</label>
<input type="password"
name="confirm"
value={confirm}
onChange={(event) => setConfirm(event.target.value)}
/>
 
{bothFieldsAreFilled ?
(passwordsMatch ?
(
<div>
<i className="material-icons">check_circle</i>
Passwords match
</div>
) :
(
<div>
<i className="material-icons">report</i>
Passwords do not match
</div>
)
) : null
}
</form>
);
}

If you’re not a React developer, this syntax might be a bit different than what you’re used to.

React handles conditional rendering differently from, say, Vue. In Vue, you would add in the v-if/v-else directives to determine whether or not a particular element should be displayed. Since directives don’t actually exist in React, we have to use a different approach. So instead of a directive, we embed our conditional logic directly into the JSX and use that to determine what should be rendered and when.

Just an FYI: we’re using a ternary operator here, but if/else statements and logical operators (&&/||) are also fair game.

And there you have it. Now we have a working component! Hopefully this has made React hooks a bit less scary to approach.

Dan Sheetz

Dan Sheetz

Partner + Managing Director

Hey, I’m Dan!

I spend my days helping businesses at key moments in their evolution become the massively successful, software-propelled businesses they were meant to be.