---
title: Bitovi Academy - Managing State in React
description: Work with React’s useState Hook to manage a component’s state.
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React Native ](https://www.bitovi.com/academy/learn-react-native.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us) 
    - [ Introduction to React ](https://www.bitovi.com/academy/learn-react/intro-to-react.html?hsLang=en-us)
    - [ Setting Up Your Environment ](https://www.bitovi.com/academy/learn-react/setting-up-your-environment.html?hsLang=en-us)
    - [ Introduction to JSX ](https://www.bitovi.com/academy/learn-react/intro-to-jsx.html?hsLang=en-us)
    - [ Building Components ](https://www.bitovi.com/academy/learn-react/components.html?hsLang=en-us)
    - [ Passing Props ](https://www.bitovi.com/academy/learn-react/props.html?hsLang=en-us)
    - [ Routing in React ](https://www.bitovi.com/academy/learn-react/routing.html?hsLang=en-us)
    - [ Styling in React ](https://www.bitovi.com/academy/learn-react/styling-in-react.html?hsLang=en-us)
    - [ Managing State in React ](https://www.bitovi.com/academy/learn-react/stateful-hooks.html?hsLang=en-us)
    - [ Making HTTP Requests ](https://www.bitovi.com/academy/learn-react/making-http-requests.html?hsLang=en-us)
    - [ Creating Nested Routes ](https://www.bitovi.com/academy/learn-react/nested-routes.html?hsLang=en-us)
    - [ Handling User Inputs and Forms ](https://www.bitovi.com/academy/learn-react/controlled-vs-uncontrolled.html?hsLang=en-us)
    - [ Testing in React ](https://www.bitovi.com/academy/learn-react/testing.html?hsLang=en-us)
    - [ Additional Resources ](https://www.bitovi.com/academy/learn-react/additional-resources.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# Managing State in React `page`

Work with React’s useState Hook to manage a component’s state.

## Overview

In this section, we will:

- Get an overview of state management
- Cover the fundamentals of React Hooks
- Review the Rules of Hooks
- Learn about the `useState` Hook
- Create custom Hooks as services

## Objective 1: Add buttons to select a state

Currently, our restaurant list is a static array of restaurants. We want to work towards having a `<select>` dropdown for choosing a *state*, then a *city* in that state, then loading the list of restaurants for that city.

To start, let’s focus on rendering buttons for each state that we can select. Then, when the button for a state is activated, we want to keep track of which state was chosen.

![A web page titled “Restaurants” from place-my-order.com showing two buttons labeled “Illinois” and “Wisconsin”. There is also a “Current state” paragraph that shows that no state is selected.](https://bitovi.github.io/academy/static/img/react-vite/08-stateful-hooks/01-solution.png)

### Overview of state management

State in React is a crucial concept, as it represents the parts of an app that can change over time. Each component can have its own state, allowing them to maintain and manage their own data independently. When the state changes, React re-renders the component and updates the DOM if it needs to.

There are different types of state within an application:

- **URL State:** The state that exists on our URLs, including pathname and query parameters. We already covered this in our section about Routing!
- **Global State:** This refers to data that is shared between multiple components. In React, global state can be managed using Context API or state management libraries; this is out of scope for this training.
- **Local State:** This is data we manage in the component that uses it. Local state is managed in React using the `useState` Hook, which we will cover in Objective 2 below.
- **UI State:** This is a subset of Local State, but is limited to minor UI effects, such as the visibility of a drawer or open section of an accordion.

### Intro to React Hooks

We’ve mentioned before that `useState` is a Hook for managing state, but what does that mean?

React Hooks (referred to as just Hooks for the rest of this training) are special functions that allow us to “hook” into React functionality. Hooks provide us with many conveniences like sharing stateful logic between components and simplifying what would be otherwise complex components.

We’ve actually already seen and used a Hook while building Place My Order! Do you remember this code from earlier?

```
import { Link, Outlet, useMatch } from "react-router-dom"

const App: React.FC = () => {
  const homeMatch = useMatch("/")
  const restaurantsMatch = useMatch("/restaurants")

  return (
    <>
      <header>
        <nav>
          <h1>place-my-order.com</h1>
          <ul>
            <li className={homeMatch ? "active" : ""}>
              <Link to="/">Home</Link>
            </li>
            <li className={restaurantsMatch ? "active" : ""}>
              <Link to="/restaurants">Restaurants</Link>
            </li>
          </ul>
        </nav>
      </header>

      <Outlet />
    </>
  )
}

export default App

```

The `useMatch` Hook from `react-router-dom` allowed us to check whether a given path “matched” the current route.

### The Rules of Hooks

React imposes several rules around the use of Hooks:

- **First,** only call Hooks from React function components or your own custom Hooks.
- **Second,** all the Hooks in a React function must be invoked in the same order every time the function runs, so no Hooks can occur after an `if`, `loop`, or `return` statement. Typically this means all Hooks are placed at the top of the React function body.
- **Third,** Hooks must be named by prefixing their functionality with `use` (e.g. `useMatch`).

### The useState Hook

We can store state that persists through component rendering with the `useState` hook. You can set the initial state value when the component **first** renders by providing the value as an argument to the Hook. *If you do not provide a value the initial state value will be `undefined`.*

This example shows a `useState` Hook being set with an initial value of `"Auto"`:

```
import { useState } from "react"

const Settings: React.FC = () => {
  const [theme, setTheme] = useState("Auto")

  const updateTheme = (newTheme) => {
    console.info("Updating theme:", newTheme)
    setTheme(newTheme)
  }

  return (
    <main>
      <p>Current theme: {theme}</p>
      <button onClick={() => updateTheme("Light")}>Set light mode</button>
      <button onClick={() => updateTheme("Dark")}>Set dark mode</button>
      <button onClick={() => updateTheme("Auto")}>Set theme to auto</button>
    </main>
  )
}

export default Settings

```

As you can see in the previous example, `useState` returns an array with two elements: the first is the current state value of the Hook, and the second is a setter function that is used to update the state value.

In the following code, the value is being rendered and the setter is being used to keep track of which theme is chosen:

```
import { useState } from "react"

const Settings: React.FC = () => {
  const [theme, setTheme] = useState("Auto")

  const updateTheme = (newTheme) => {
    console.info("Updating theme:", newTheme)
    setTheme(newTheme)
  }

  return (
    <main>
      <p>Current theme: {theme}</p>
      <button onClick={() => updateTheme("Light")}>Set light mode</button>
      <button onClick={() => updateTheme("Dark")}>Set dark mode</button>
      <button onClick={() => updateTheme("Auto")}>Set theme to auto</button>
    </main>
  )
}

export default Settings

```

Every time a `useState`’s setter is invoked with a new value, React compares the new value with the current value. If the values are the same, nothing happens; **if the values are different, React will rerender the component** so the new state value can be used to update the component.

In the example above, when the user makes a selection, the `Settings` component is rendered again, and the paragraph is updated with the current value.

### Setup 1

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to include the State and City dropdown lists.

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"

const RestaurantList: React.FC = () => {
  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button key={short} type="button">
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {"(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

### Verify 1

These tests will pass when the solution has been implemented properly.

✏️ Update **src/pages/RestaurantList/RestaurantList.test.tsx**:

```
import "@testing-library/jest-dom"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { describe, expect, it } from "vitest"

import RestaurantList from "./RestaurantList"

describe("RestaurantList component", () => {
  it("renders the Restaurants header", () => {
    render(<RestaurantList />)
    expect(screen.getByText(/Restaurants/i)).toBeInTheDocument()
  })

  it("renders the restaurant images", () => {
    render(<RestaurantList />)
    const images = screen.getAllByRole("img")
    expect(images[0]).toHaveAttribute(
      "src",
      expect.stringContaining("2-thumbnail.jpg"),
    )
    expect(images[0]).toHaveAttribute("width", "100")
    expect(images[0]).toHaveAttribute("height", "100")
    expect(images[1]).toHaveAttribute(
      "src",
      expect.stringContaining("4-thumbnail.jpg"),
    )
    expect(images[1]).toHaveAttribute("width", "100")
    expect(images[1]).toHaveAttribute("height", "100")
  })

  it("renders the addresses", () => {
    render(<RestaurantList />)
    const addressDivs = screen.getAllByText(/Washburne Ave|Kinzie Street/i)
    expect(addressDivs[0]).toHaveTextContent("2451 W Washburne Ave")
    expect(addressDivs[0]).toHaveTextContent("Green Bay, WI 53295")
    expect(addressDivs[1]).toHaveTextContent("230 W Kinzie Street")
    expect(addressDivs[1]).toHaveTextContent("Green Bay, WI 53205")
  })

  it("renders the hours and price information for each restaurant", () => {
    render(<RestaurantList />)
    const hoursPriceDivs = screen.getAllByText(/\$\$\$/i)
    hoursPriceDivs.forEach((div) => {
      expect(div).toHaveTextContent("$$$")
      expect(div).toHaveTextContent("Hours: M-F 10am-11pm")
    })
  })

  it("indicates if the restaurant is open now for each restaurant", () => {
    render(<RestaurantList />)
    const openNowTags = screen.getAllByText("Open Now")
    expect(openNowTags.length).toBeGreaterThan(0)
  })

  it("renders the details buttons with correct links for each restaurant", () => {
    render(<RestaurantList />)
    const detailsButtons = screen.getAllByRole("link")
    expect(detailsButtons[0]).toHaveAttribute(
      "href",
      "/restaurants/cheese-curd-city",
    )
    expect(detailsButtons[1]).toHaveAttribute(
      "href",
      "/restaurants/poutine-palace",
    )
    detailsButtons.forEach((button) => {
      expect(button).toHaveTextContent("Details")
    })
  })

  it("renders the component", () => {
    render(<RestaurantList />)
    expect(screen.getByText("Restaurants")).toBeInTheDocument()
    expect(screen.getByText("State:")).toBeInTheDocument()
  })

  it("allows state selection and updates cities accordingly", async () => {
    render(<RestaurantList />)

    const illinoisButton = screen.getByText("Illinois")
    await userEvent.click(illinoisButton)

    expect(screen.getByText("Current state: IL")).toBeInTheDocument()
    expect(
      screen.queryByText("Choose a state before selecting a city"),
    ).not.toBeInTheDocument()
  })

  it("renders ListItem components for each restaurant", () => {
    render(<RestaurantList />)

    const restaurantNames = screen.getAllByText(
      /Cheese Curd City|Poutine Palace/,
    )
    expect(restaurantNames.length).toBe(2)
  })
})

```

### Exercise 1

Let’s create buttons for each state that we can select. Then, when the button for a state is activated, we want to keep track of which state was choosen.

- Call `useState()` to get a `state` variable and `setState` setter.
- Create a helper function that takes a `short` state name and calls `setState`.
- Add an `onClick` handler to the `button` that calls your helper function.
- Update the paragraph to show the currently-selected state.

**Having issues with your local setup?** You can use either [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/01-problem?file=src/pages/RestaurantList/RestaurantList.tsx) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/01-problem?file=src/pages/RestaurantList/RestaurantList.tsx) to do this exercise in an online code editor.

### Solution 1

Click to see the solution

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be:

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")

  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  const updateState = (stateShortCode: string) => {
    setState(stateShortCode)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button
                key={short}
                onClick={() => updateState(short)}
                type="button"
              >
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {state || "(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

**Having issues with your local setup?** See the solution in [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/01-solution?file=src/pages/RestaurantList/RestaurantList.tsx) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/01-solution?file=src/pages/RestaurantList/RestaurantList.tsx).

## Objective 2: Add buttons to select a city

Now that we have buttons for selecting the state, let’s add buttons for selecting the city:

![The same “Restaurants” web page from before, but this time the “Current state” is “Illinois” and there is a button for selecting “Springfield” as the city. The “Current city” is currently “none.”](https://bitovi.github.io/academy/static/img/react-vite/08-stateful-hooks/02-solution-2.png)

After selecting both the state and city, we will see those values reflected in our UI:

![The same “Restaurants” web page from before, but this time the “Current city” is set to “Springfield”.](https://bitovi.github.io/academy/static/img/react-vite/08-stateful-hooks/02-solution-3.png)

### Setup 2

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be the following:

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")

  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const cities = [
    { name: "Madison", state: "WI" },
    { name: "Springfield", state: "IL" },
  ]

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  const updateState = (stateShortCode: string) => {
    setState(stateShortCode)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button
                key={short}
                onClick={() => updateState(short)}
                type="button"
              >
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {state || "(none)"}</p>
          </div>

          <div className="form-group">
            City:
            {state ? (
              cities.map(({ name }) => (
                <button key={name} type="button">
                  {name}
                </button>
              ))
            ) : (
              <> Choose a state before selecting a city</>
            )}
            <hr />
            <p>Current city: {"(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

### Verify 2

These tests will pass when the solution has been implemented properly.

✏️ Update **src/pages/RestaurantList/RestaurantList.test.tsx** to be the following:

```
import "@testing-library/jest-dom"
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { describe, expect, it } from "vitest"

import RestaurantList from "./RestaurantList"

describe("RestaurantList component", () => {
  it("renders the Restaurants header", () => {
    render(<RestaurantList />)
    expect(screen.getByText(/Restaurants/i)).toBeInTheDocument()
  })

  it("renders the restaurant images", () => {
    render(<RestaurantList />)
    const images = screen.getAllByRole("img")
    expect(images[0]).toHaveAttribute(
      "src",
      expect.stringContaining("2-thumbnail.jpg"),
    )
    expect(images[0]).toHaveAttribute("width", "100")
    expect(images[0]).toHaveAttribute("height", "100")
    expect(images[1]).toHaveAttribute(
      "src",
      expect.stringContaining("4-thumbnail.jpg"),
    )
    expect(images[1]).toHaveAttribute("width", "100")
    expect(images[1]).toHaveAttribute("height", "100")
  })

  it("renders the addresses", () => {
    render(<RestaurantList />)
    const addressDivs = screen.getAllByText(/Washburne Ave|Kinzie Street/i)
    expect(addressDivs[0]).toHaveTextContent("2451 W Washburne Ave")
    expect(addressDivs[0]).toHaveTextContent("Green Bay, WI 53295")
    expect(addressDivs[1]).toHaveTextContent("230 W Kinzie Street")
    expect(addressDivs[1]).toHaveTextContent("Green Bay, WI 53205")
  })

  it("renders the hours and price information for each restaurant", () => {
    render(<RestaurantList />)
    const hoursPriceDivs = screen.getAllByText(/\$\$\$/i)
    hoursPriceDivs.forEach((div) => {
      expect(div).toHaveTextContent("$$$")
      expect(div).toHaveTextContent("Hours: M-F 10am-11pm")
    })
  })

  it("indicates if the restaurant is open now for each restaurant", () => {
    render(<RestaurantList />)
    const openNowTags = screen.getAllByText("Open Now")
    expect(openNowTags.length).toBeGreaterThan(0)
  })

  it("renders the details buttons with correct links for each restaurant", () => {
    render(<RestaurantList />)
    const detailsButtons = screen.getAllByRole("link")
    expect(detailsButtons[0]).toHaveAttribute(
      "href",
      "/restaurants/cheese-curd-city",
    )
    expect(detailsButtons[1]).toHaveAttribute(
      "href",
      "/restaurants/poutine-palace",
    )
    detailsButtons.forEach((button) => {
      expect(button).toHaveTextContent("Details")
    })
  })

  it("renders the component", () => {
    render(<RestaurantList />)
    expect(screen.getByText("Restaurants")).toBeInTheDocument()
    expect(screen.getByText("State:")).toBeInTheDocument()
  })

  it("allows state selection and updates cities accordingly", async () => {
    render(<RestaurantList />)

    const illinoisButton = screen.getByText("Illinois")
    await userEvent.click(illinoisButton)

    expect(screen.getByText("Current state: IL")).toBeInTheDocument()
    expect(
      screen.queryByText("Choose a state before selecting a city"),
    ).not.toBeInTheDocument()
  })

  it("allows city selection after a state is selected", async () => {
    render(<RestaurantList />)

    const illinoisButton = screen.getByText("Illinois")
    await userEvent.click(illinoisButton)

    const greenBayButton = screen.getByText("Springfield")
    await userEvent.click(greenBayButton)

    expect(screen.getByText("Current city: Springfield")).toBeInTheDocument()
  })

  it("renders ListItem components for each restaurant", () => {
    render(<RestaurantList />)

    const restaurantNames = screen.getAllByText(
      /Cheese Curd City|Poutine Palace/,
    )
    expect(restaurantNames.length).toBe(2)
  })
})

```

### Exercise 2

Similar to our state buttons, let’s create buttons for selecting a city.

- Call `useState()` to get a `city` variable and `setCity` setter.
- Filter the `cities` list based on which state is selected.
- Create a helper function that takes a `cityName` and calls `setCity`.
- Add an `onClick` handler to the `button` that calls your helper function.
- Update the paragraph to show the currently-selected city.

Hint: Use [`Array.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) to narrow down the list of cities based on which state is selected.

**Having issues with your local setup?** You can use either [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/02-problem?file=src/pages/RestaurantList/RestaurantList.tsx) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/02-problem?file=src/pages/RestaurantList/RestaurantList.tsx) to do this exercise in an online code editor.

### Solution 2

Click to see the solution

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be:

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")
  const [city, setCity] = useState("")

  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const cities = [
    { name: "Madison", state: "WI" },
    { name: "Springfield", state: "IL" },
  ].filter((city) => {
    return city.state === state
  })

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  const updateState = (stateShortCode: string) => {
    setState(stateShortCode)
    setCity("")
  }

  const updateCity = (cityName: string) => {
    setCity(cityName)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button
                key={short}
                onClick={() => updateState(short)}
                type="button"
              >
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {state || "(none)"}</p>
          </div>

          <div className="form-group">
            City:
            {state ? (
              cities.map(({ name }) => (
                <button
                  key={name}
                  onClick={() => updateCity(name)}
                  type="button"
                >
                  {name}
                </button>
              ))
            ) : (
              <> Choose a state before selecting a city</>
            )}
            <hr />
            <p>Current city: {city || "(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

**Having issues with your local setup?** See the solution in [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/02-solution?file=src/pages/RestaurantList/RestaurantList.tsx) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/02-solution?file=src/pages/RestaurantList/RestaurantList.tsx).

## Objective 3: Refactor cities into a custom Hook

Our `RestaurantList.tsx` file has started to get long again. Let’s refactor the cities code into its own custom Hook so our code is more maintainable.

### What are custom Hooks?

React’s Hooks API provides a powerful and flexible way to encapsulate and reuse functionality across our components. While React comes with a set of built-in Hooks, we can also create our own custom Hooks. This allows us to abstract component logic into reusable functions. Custom Hooks are particularly useful when we find ourselves repeating the same logic in multiple components.

Custom Hooks are JavaScript functions that can use other React Hooks and provide a way to share logic across multiple components. Like built-in Hooks, custom Hooks must adhere to React’s rules of Hooks. The naming convention for custom Hooks is to start with `use`, like `useCustomHook`.

### Why use custom Hooks?

Putting stateful logic into a custom Hook has numerous benefits:

**Reusability:** One of the primary reasons for creating custom Hooks is reusability. You might find yourself repeating the same logic in different components—for example, fetching data from an API, handling form input, or managing a subscription. By refactoring this logic into a custom Hook, you can easily reuse this functionality across multiple components, keeping your code DRY (Don’t Repeat Yourself).

**Separation of concerns:** Custom Hooks allow you to separate complex logic from the component logic. This makes your main component code cleaner and more focused on rendering UI, while the custom Hook handles the business logic or side effects. It aligns well with the principle of single responsibility, where a function or module should ideally do one thing only.

**Easier testing and maintenance:** Isolating logic into custom Hooks can make your code easier to test and maintain. Since Hooks are just JavaScript functions, they can be tested independently of any component. This isolation can lead to more robust and reliable code.

**Simplifying components:** If your component is becoming too large and difficult to understand, moving some logic to a custom Hook can simplify it. This not only improves readability but also makes it easier for other developers to grasp what the component is doing.

### How to create a custom Hook

To create a custom Hook, you start by defining a function that starts with `use`. This Hook can call other Hooks and return whatever value is necessary.

Let’s create a Hook that keeps track of a boolean state, and also provides a function for toggling that state:

```
import { useState } from "react"

export function useToggle(intialValue = true) {
  const [on, setOn] = useState(intialValue)

  const handleToggle = (value?: unknown) => {
    if (typeof value === "boolean") {
      setOn(value)
    } else {
      setOn(!on)
    }
  }

  return [on, handleToggle]
}

```

In the example above, you can see that our `useToggle` Hook is a function that has an internal `useState` to keep track of the toggle’s on/off status. This hook has a `handleToggle` function for changing its internal state. Lastly, we can see that the `useToggle` Hook returns an array with the `on` status and the `handleToggle` function.

#### How to use a custom Hook

How would we use this Hook? Let’s take a look at this example:

```
import { useToggle } from "./useToggle"

const Toggle: React.FC = () => {
  const [active, toggleActive] = useToggle(true)

  return (
    <form>
      <label className="toggle">
        <input
          className="toggle-checkbox"
          checked={active}
          onChange={toggleActive}
          type="checkbox"
        />
        <div className="toggle-switch"></div>
        <span className="toggle-label">{active ? "On" : "Off"}</span>
      </label>
    </form>
  )
}

export default Toggle

```

In this component, we call our `useToggle` Hook with the initial state (`true`). Our Hook returns the `active` state and `toggleActive` function for changing the on/off state.

We will learn more about binding the `input` values in a later section, but for now the takeaway is that we can create our custom `useToggle` Hook and call it in our components, just like React’s built-in Hooks!

### Setup 3

✏️ Create **src/services/** (folder)

✏️ Create **src/services/pmo/** (folder)

✏️ Create **src/services/pmo/restaurant/** (folder)

✏️ Create **src/services/pmo/restaurant/hooks.ts** and update it to be:

```
import type { City } from "./interfaces"

export function useCities(state: string): City[] {
  return []
}

```

✏️ Create **src/services/pmo/restaurant/interfaces.ts** and update it to be:

```
export interface City {
  name: string
  state: string
}

export interface State {
  name: string
  short: string
}

```

✏️ Create **src/services/pmo/restaurant/index.ts** and update it to be:

```
export * from "./interfaces"
export * from "./hooks"

```

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be:

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"
import { useCities } from "../../services/pmo/restaurant"

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")
  const [city, setCity] = useState("")

  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const cities = [
    { name: "Madison", state: "WI" },
    { name: "Springfield", state: "IL" },
  ].filter((city) => {
    return city.state === state
  })

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  const updateState = (stateShortCode: string) => {
    setState(stateShortCode)
    setCity("")
  }

  const updateCity = (cityName: string) => {
    setCity(cityName)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button
                key={short}
                onClick={() => updateState(short)}
                type="button"
              >
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {state || "(none)"}</p>
          </div>

          <div className="form-group">
            City:
            {state ? (
              cities.map(({ name }) => (
                <button
                  key={name}
                  onClick={() => updateCity(name)}
                  type="button"
                >
                  {name}
                </button>
              ))
            ) : (
              <> Choose a state before selecting a city</>
            )}
            <hr />
            <p>Current city: {city || "(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

### Verify 3

✏️ Create **src/services/pmo/restaurant/hooks.test.ts** and update it to be:

```
import { renderHook } from "@testing-library/react"
import { describe, expect, it } from "vitest"
import { useCities } from "./hooks"

describe("useCities Hook", () => {
  it("should return cities from Wisconsin when state is WI", () => {
    const { result } = renderHook(() => useCities("WI"))
    expect(result.current).toHaveLength(1)
    expect(result.current[0].name).toBe("Madison")
  })

  it("should return cities from Illinois when state is IL", () => {
    const { result } = renderHook(() => useCities("IL"))
    expect(result.current).toHaveLength(1)
    expect(result.current[0].name).toBe("Springfield")
  })

  it("should return no cities for an unknown state", () => {
    const { result } = renderHook(() => useCities("CA"))
    expect(result.current).toHaveLength(0)
  })
})

```

### Exercise 3

- Move the `cities` logic (including the filtering) into our custom `useCities()` Hook.
- Update the `<RestaurantList>` component to use the new `useCities()` Hook.

**Having issues with your local setup?** You can use either [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/03-problem?file=src/services/pmo/restaurant/hooks.ts) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/03-problem?file=src/services/pmo/restaurant/hooks.ts) to do this exercise in an online code editor.

### Solution 3

Click to see the solution

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be:

```
import CheeseThumbnail from "place-my-order-assets/images/2-thumbnail.jpg"
import PoutineThumbnail from "place-my-order-assets/images/4-thumbnail.jpg"
import { useState } from "react"
import ListItem from "./ListItem"
import { useCities } from "../../services/pmo/restaurant"

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")
  const [city, setCity] = useState("")

  const states = [
    { name: "Illinois", short: "IL" },
    { name: "Wisconsin", short: "WI" },
  ]

  const cities = useCities(state)

  const restaurants = {
    data: [
      {
        name: "Cheese Curd City",
        slug: "cheese-curd-city",
        images: {
          thumbnail: CheeseThumbnail,
        },
        address: {
          street: "2451 W Washburne Ave",
          city: "Green Bay",
          state: "WI",
          zip: "53295",
        },
        _id: "Ar0qBJHxM3ecOhcr",
      },
      {
        name: "Poutine Palace",
        slug: "poutine-palace",
        images: {
          thumbnail: PoutineThumbnail,
        },
        address: {
          street: "230 W Kinzie Street",
          city: "Green Bay",
          state: "WI",
          zip: "53205",
        },
        _id: "3ZOZyTY1LH26LnVw",
      },
    ],
  }

  const updateState = (stateShortCode: string) => {
    setState(stateShortCode)
    setCity("")
  }

  const updateCity = (cityName: string) => {
    setCity(cityName)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <div className="form-group">
            State:
            {states.map(({ short, name }) => (
              <button
                key={short}
                onClick={() => updateState(short)}
                type="button"
              >
                {name}
              </button>
            ))}
            <hr />
            <p>Current state: {state || "(none)"}</p>
          </div>

          <div className="form-group">
            City:
            {state ? (
              cities.map(({ name }) => (
                <button
                  key={name}
                  onClick={() => updateCity(name)}
                  type="button"
                >
                  {name}
                </button>
              ))
            ) : (
              <> Choose a state before selecting a city</>
            )}
            <hr />
            <p>Current city: {city || "(none)"}</p>
          </div>
        </form>

        {restaurants.data ? (
          restaurants.data.map(({ _id, address, images, name, slug }) => (
            <ListItem
              key={_id}
              address={address}
              name={name}
              slug={slug}
              thumbnail={images.thumbnail}
            />
          ))
        ) : (
          <p>No restaurants.</p>
        )}
      </div>
    </>
  )
}

export default RestaurantList

```

✏️ Update **src/services/pmo/restaurant/hooks.ts** to be:

```
import type { City } from "./interfaces"

export function useCities(state: string): City[] {
  const cities = [
    { name: "Madison", state: "WI" },
    { name: "Springfield", state: "IL" },
  ]
  return cities.filter((city) => {
    return city.state === state
  })
}

```

**Having issues with your local setup?** See the solution in [StackBlitz](https://stackblitz.com/fork/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/03-solution?file=src/services/pmo/restaurant/hooks.ts) or [CodeSandbox](https://codesandbox.io/p/devbox/github/bitovi/academy/tree/main/exercises/react-vite/08-stateful-hooks/03-solution?file=src/services/pmo/restaurant/hooks.ts).

## Next steps

Next, let’s learn how to [make HTTP requests](https://www.bitovi.com/academy/learn-react/making-http-requests.html?hsLang=en-us) with `fetch` in React applications.

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
     "@context": "http://schema.org",
     "@type": "Organization",
     "mainEntityOfPage":{
          "@type":"WebPage",
          "@id":"https://www.bitovi.com/academy/learn-react/stateful-hooks.html",
          "description": "Work with React’s useState Hook to manage a component’s state. 
"
     },
     "url": "http://bitovi.com",
     "logo": "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
     "image": "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
     "name": "Bitovi Web App Consulting",
     "alternateName": "Bitovi",
      "naics": "541511",
      "description": "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
     "address": {
          "@type": "PostalAddress",
          "streetAddress": "1134 Pine Tree Lane ",
          "addressLocality": "Libertyville",
          "addressRegion": "IL",
          "addressCountry": "United States",
          "postalCode": "60048"
     },
     "areaServed": {
          "@type": "GeoCircle",
          "geoMidpoint": {
               "@type": "GeoCoordinates",
               "latitude": "41.8781",
               "longitude": "87.6298"
          },
          "geoRadius": "5000 km"
     },
     "telephone": "312-620-0386",
     "email": "contact@bitovi.com",
     "sameAs": [
          "https://www.facebook.com/BitoviLLC/",
          "https://twitter.com/bitovi",
          "https://www.linkedin.com/company/bitovi"
     ]
}
```