TNS
VOXPOP
As a JavaScript developer, what non-React tools do you use most often?
Angular
0%
Astro
0%
Svelte
0%
Vue.js
0%
Other
0%
I only use React
0%
I don't use JavaScript
0%
NEW! Try Stackie AI
C++ / Frontend Development / Programming Languages

How to Build Multistep Forms in React

A simple, practical way to handle multistep forms with no heavy libraries, just React hooks.
May 22nd, 2025 12:00pm by
Featued image for: How to Build Multistep Forms in React
Image from Julia Tim on Shutterstock

Multistep forms are common. You’ll see them in onboarding, checkout, quizzes and setup wizards. Each step collects a small part of the full information. Yet implementing them cleanly in React remains a challenge. We developers frequently encounter issues such as:

  • State sprawl — managing step state and form data separately.
  • Unnecessary rerenders — a change in one step can trigger renders in unrelated components.
  • Rigid architecture — Adding validation or dynamic logic often requires major rewrites.

Libraries like Context API, Redux or Zustand are popular choices. But they come with trade-offs like extra setup or unnecessary re-renders. These can get in the way when you just need simple, local state management.

This tutorial provides a simple, practical way to handle multistep forms. No heavy libraries, just React hooks. This custom solution stays clean and efficient.

It keeps logic local, prevents extra re-renders and stays flexible for real-world forms. It’s lightweight yet powerful.

Which State Managers to Avoid for Multistep Forms?

State libraries are great for global state but don’t fit well for form flows. Their global nature adds unnecessary complexity, which can also hurt performance, especially in multistep forms. They can cause extra re-renders across components, slowing things down. Plus, the added boilerplate makes managing simple multistep forms unnecessarily complex.

To address these challenges, a more customized state management solution is necessary. By isolating the state per step, we avoid unnecessary updates across the form. This keeps the form fast, responsive and simple to manage, even in larger applications.

Image showing the advantages of custom state management over the context API.

Image showing the advantages of custom state management over the context API.

Step 0: Prerequisite

To get started, you must have:

  1. NodeJs installed
  2. Basic JavaScript knowledge
  3. React knowledge

Step 1: Creating the React Project

You must have a React project ready.

If you don’t have a project running, you can create a new TypeScript React project using Vite. It’s recommended for fast build times. Run this command to get started:


Start the development server to confirm everything works by running the command below:

npm run dev

Before creating new files, let’s look at the file structure that we are going to create.

 Image showing the file structure of the React project with a multistep form setup.

Image showing the file structure of the React project with a multistep form setup.

Step 2: Setting Up the Store

Create a new folder in src, name it service and add a new file under it named customStore.ts.


This code creates a lightweight state management system to handle global state without using context or third-party libraries. Defining a store that holds the state lets us update it through an updater function and gives access to the current value.

Adding a subscription system with selectors allows components to listen only to the specific slice of state they need. This helps avoid unnecessary re-renders and keeps components efficient. It also makes state management more modular and scalable. Whenever that selected state changes, the corresponding callback is triggered.

The result is a clean, minimal setup that works seamlessly in React environments.

Next, create a new folder in src, calling it hooks for adding useStore.ts in it.


In this code, a custom hook called useStore links React components with our custom store. The local state is initialized by using the selector. This extracts only the specific piece of global state needed by the component.

A subscription inside a useEffect listens for changes to the selected state. Whenever the relevant part of the store updates, the component’s state gets updated.

This approach keeps components updated with the store. They only re-render when the specific data they need changes.

Step 3: Creating the Multistep Form

Create a basic user onboarding form with three basic steps as follows:

  1. Personal information
  2. Account setup
  3. Preferences

Step 4: Initializing the Form Store

First, initialize the form state that will hold all the form data. Create a new file in the service folder called formStore.ts.


This defined the shape of the form state and set an initial structure with default values. Using createStore makes the store shareable across components.

Step 5: Creating Navigation for a Multistep Form

First, create a new file StepNavigation.tsx in a components folder under the src folder.


This code builds a StepNavigation component. It reads the current step from the form store using the useStore hook. This ensures that the component only re-renders when the current step changes. This approach keeps it efficient and reactive.

It also defined a goToStep function, which updates the store’s currentStep using updateState. This allows users to jump between steps by clicking the corresponding buttons.

This links UI interactions directly to the shared state. It keeps navigation simple, centralized and in sync with the form.

Step 6: Creating the Forms

Next, start adding steps.

Form 1: Personal Info

Create a new folder called steps and add these step components. The first is PersonalInfoStep.tsx.


The custom store hook fetches personal form data and manages local form state. Inputs were controlled, and on submit, its updates the store and moves to the next step.

Image showing the first step in the multistep form.

This is how the first form looks.

Form 2: Account Setup

Now create a second step file called AccountSetupStep.tsx.


The second step is similar to the first, adding a back button for navigation.

Image showing the second step in the multistep form.

Form 3: Preferences

Finally, create the PreferencesStep.tsx component.


This is the final component. The current preference settings are pulled from the store the stored in local state, making it easier to manage user input.

Then checkboxes and radio buttons reflect and update the preference data. On submission, the changes are pushed back into the store and a success alert displays. A back button allows users to return to the previous step.

Image showing the third step in the multistep form.
Image showing the third step in the multistep form.

The form is completed. The UI stayed intuitive, and the centralized state is maintained throughout the flow.

Step 7: Bringing Everything Together

Create a new file directly under the components folder called MultiStepForm.tsx.


MultiStepForm ties everything together, reading the current step from the store. Based on this, it renders the corresponding step component.

Adding StepNavigation at the top allows quick switching between steps. Everything stays in sync through the shared store.

Finally, update App.tsx to see it work.

Performance Considerations and Key Benefits

Let’s examine why this custom solution outperforms traditional approaches:

  • Granular reactivity: Context API triggers re-renders in all components that consume the state. In contrast, selector-based subscriptions update only the components that need to change. This is especially useful in multistep forms, where each step handles separate data.
  • Zero dependency architecture: Relying on React hooks and basic JavaScript avoids extra libraries. The entire solution is under 2KB, much smaller than the 10KB+ size of most state libraries.
  • Type safety by design: TheTypeScript setup ensures strong type inference. Selectors automatically pass types to components. This catches errors at compile time when accessing store properties.
  • Progressive enhancement: Need persistence? Add a middleware layer to sync with localStorage. Require undo/redo? Implement a state history buffer. The minimal core makes these extensions straightforward.

Final Implementation Notes

For production use, you might want to:

  • Add store persistence (such as to localStorage)
  • Implement middleware for logging or validation
  • Create helper hooks like useStoreActions for common mutations
  • Add the batch updates for complex state transitions

These enhancements can be added incrementally without refactoring existing components.

That’s it! With this approach, you can create a customized multistep form. It also ensures efficient store management for better performance.

Conclusion

This state management approach hits that sweet spot between keeping things simple and having enough horsepower when you need it. It’s perfect for those medium-complexity projects, such as those multistep forms that always seem to get unwieldy.

I love that it ditches all the Context API boilerplate that used to drive me crazy. The performance boost is noticeable too, and it just makes the development process much less painful.

I’ve found it works especially well for:

  • Multistep forms that tend to grow out of control
  • Those wizard interfaces where users step through a process
  • Settings screens with interconnected options
  • Really any situation where different components need their own slice of the state pie.

Give it a try on your next project; it’s lightweight, performs well, and won’t turn your code base into spaghetti as you scale up.

Discover other ways React can transform your tech projects by reading Andela’s guide “Building a Dockerized Todo App With React, Chakra UI and Rust.

Created with Sketch.
TNS owner Insight Partners is an investor in: Enable.
TNS DAILY NEWSLETTER Receive a free roundup of the most recent TNS articles in your inbox each day.