Rhythm & Binary

Understanding useState in React

lego blocks

If you've worked with React (or are even new to it) you've probably heard of the useState hook. The useState hook is one of the most fundamental parts of the React library, and also greatly improves data management in an application.

This post is going to walkthrough what useState is and more generically how you can use it and other hooks in React development. If you want to follow along, I recommend checking out my sample project on GitHub at https://github.com/andrewevans0102/react-use-state-examples2. I'm also presenting at one of Log Rocket's virtual Meetups on Janary 16 at 12 PM EST on this same topic. Sign up for the meetup online at https://blog.logrocket.com/understanding-usestate-in-react/.

What are React Components?

When working with React, one of the first things you usually find is someone referring to a "hook" for development. According to the React Documentation React Hooks are reusable stateful logic that can reused in different components.

By React "components" we are referring to small pieces of a user interface. This could be an entire page or just the header, they really can be whatever you want to define them as. Usually you see something like the following:

// someComponent.tsx
const someComponent = () => {

  return (
    <div>
      <p>Hello World</p>
    </div>
   );
}

This example is obviously ver simplified, but you basically just have a function definition which then returns whatever part you are defining of a webpage. Components can have `props` that can be passed into a component to use. You can then define hierarchies of components where you have a parent with children similar to the following:

// parentComponent.tsx

import { ChildComponent } from './components/ChildComponent';

const parentComponent = (props: any) => {

  return(
    <div>
      <h1>Hello World</h1>
      <ChildComponent />
    </div>
   );
}

Again, these are super simple but you can see how components can be used to build a part or a whole Frontend with React.

Inside these components, you have the concept of "state" which can be anything from a local value to something connected with a centralized state management library like Redux. A simple component that just has one thing managed in its local state, a variable called "title" that stores the value "hello world" like the following:s

const HelloComponent = () => {
    const title = 'hello world';

    return (
        <div>
            <p>{title}</p>
        </div>
    );
};

Now you may want the "title" value to be editable by the user, or to change it programatically. This is where the useState hook (and hooks more generically speaking) are important.

React's useState hook is built in and allows you to manipulate a component's state. Considering our HelloComponent from before, lets now look at it with useState:

import { useState } from "react";

const HelloComponent = () => {
    const [title, setTitle] = useState("hello world");

    return (
        <div>
            <p>{title}</p>
        </div>
    );
};

Notice how const [title, setTitle] = useState("hello world"); replaces the original value declaration. The useState hook provides the component with the value, and a way to manipualte that value (here setTitle).

You can now wire this up to something like a Textfield or Button and then manage your component's state like the following:

import { useState } from 'react';

export const Single = () => {
    const [title, setTitle] = useState('hello world');

    return (
        <section>
            <h1>{title}</h1>
            <input
                type="text"
                onChange={(e) => {
                    setTitle(e.target.value);
                }}
                value={title}
            />
        </section>
    );
};

This results in something that looks like the following:

hello world useState component

The cool part about this is that the useState functionality can be used in any React component. This is why Hooks are so powerful. The useState component is built in, but you can also write you own hooks and use them throughout your application. Check out the official React Documentation for more on writing custom hooks.

For the sake of this article, we are going to focus on useState specifically. In a broader sense, you can write custom React Hooks that operate similart to useState and then bring them into components in a larger application.

More advanced use of useState

So in the examples I've walked through above, I made things very simple to be able to demonstrate some basic concepts. If you're looking at my sample project, you'll note that I have three components that showcase useState:

  1. Counter.tsx that just increments a number
  2. Single.tsx that changes the "hello world" text input
  3. Multiple.tsx that changes an array of "to-do" values in a component

All three of them showcase common ways that you would use the useState hook in your application.

Both Counter.tsx and Single.tsx directly call the set function like so:

                onChange={(e) => {
                    setTitle(e.target.value);
                }}

The Multiple.tsx component has handlers for the onChange of a variable input as well as state variables for the text input and the array of to-do values to output:

    const handleTodoChange = (event: any) => {
        setTodoInput(event?.target.value);
        console.log(
            `todo input was updated successfully to be ${event?.target.value}`
        );
    };

    const addTodo = (newTodo: string) => {
        const localTodos: textInput[] = todos;
        localTodos.push({ text: newTodo });
        // set value
        // use spread syntax to trigger re render
        setTodos([...localTodos]);
        console.log(
            `todo array was updated to be ${JSON.stringify(localTodos)}`
        );

        // clear input field
        setTodoInput('');
        console.log('todo input was cleared');
    };

This pattern of having an event handler and corresponding calls to update the state value is pretty common in React projects. Having a handler also helps if you have more advanced situations like you want to call an API after a value is input or you may want to sanitize the data that is typed.

In the example components I also added console.log statements so you can follow along and see the useState hook changing the values directly:

example useState components in browser

More Advanced cases and beyond

The main purpose of this post was just to introduce the useState hook. I also introduced the concepts of hooks to be able to understand hwo they work in React development.

The examples that I've shown have been necessarily simple, but with real applications you'll often find fairly complext usescases where state is passed between components. The useState hook works well with simple components that just need simple state maintained. When you get into more complex applications, you'll often have pages composed of smaller components that often will need to exchange data based on user actions and API calls. Those more complex situations often call for libraries like Redux. Libraries like Redux allow you to keep your code DRY (do not repeate yourself) and also make your life easier as you maintain one section of variables rather than individual state values in many different components.

I recommend checking out the sample project, and also looking into the way more complex applications manage state.

Thanks for reading my post!