Check your version
This post assumes you're using React Router v6. If not, find your version below.

Respecting your user’s input is not only a solid business decision, but it also shows you care about UX. One of the most frustrating experiences a user can have is when they spend time inputting data into your app, accidentally navigate away from the form, then have to start it all over again.

There are a few different approaches to fixing this UX anti-pattern, and in this post we’ll focus on how React Router specifically can help by warning the user before they transition away from the form to a new page.

Skip the setup
If you'd like to skip the setup and jump straight to the implementation of preventing transitions with React Router's Prompt component, click here.

Before we can see how it’s done, we’ll first need to set up the basic skeleton of our app. We’ll keep things simple with three components, Home, About, and Contact, mapping to our three routes, /, /about, and /contact.

import * as React from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Link
} from "react-router-dom";
const Home = () => <h1>Home</h1>;
const About = () => <h1>About</h1>;
function Form() {
return (
<form>
{'todo'}
</form>
);
}
function Contact() {
return (
<React.Fragment>
<h1>Contactus</h1>
<Form />
</React.Fragment>
);
}
export default function App() {
return (
<Router>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
<div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</Router>
);
}

Now we need to build out our Form component. The eventual goal here is to make it so if the form is dirty and the user tries to navigate away, we verify that it’s not by accident.

To do that, we’ll first need to add form state to our component. Because we’re building a contact form, we’ll have three pieces of state – name, email, and note.

function Form() {
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [note, setNote] = React.useState("");
return (
<form>
{'todo'}
</form>
);
}

Now that we’ve added our form state to the component, let’s add in some JSX so we can collect our user’s input and update that form state.

function Form() {
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [note, setNote] = React.useState("");
return (
<form
onSubmit={(e) => {
e.preventDefault();
alert("Submitted!");
setName("");
setEmail("");
setNote("");
}}
>
<label htmlFor="name">Name</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
type="text"
id="name"
placeholder="name"
/>
<label htmlFor="email">Email</label>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
type="text"
id="email"
placeholder="email"
/>
<label htmlFor="note">Note</label>
<textarea
value={note}
rows={4}
onChange={(e) => setNote(e.target.value)}
type="text"
id="note"
placeholder="note"
/>
<button type="submit">Submit</button>
</form>
);
}

React Router’s Prompt Component

At this point we have our navigation, Routes, and Form set up. The last thing we need to do is warn the user if they try to navigate away from /contact when the form is dirty. To do this, we can use React Router’s Prompt component.

Prompt receives two props – when and message. when is a boolean that if true, will show the user a prompt with the message when they try to navigate away.

In our example, we can derive when from the form state we already have (name, email, and note). We’ll abstract this into a function called isDirty.

const isDirty = () => {
return name.length > 0
|| email.length > 0
|| note.length > 0;
};

Now that we have the function which will determine when we want to prompt the user when they navigate away from /contact, the only other thing we need to do it choose a message and render the Prompt component.

import {
BrowserRouter as Router,
Routes,
Route,
Prompt,
Link
} from "react-router-dom";
...
function Form() {
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [note, setNote] = React.useState("");
const isDirty = () => {
return name.length > 0
|| email.length > 0
|| note.length > 0;
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
alert("Submitted!");
setName("");
setEmail("");
setNote("");
}}
>
<Prompt
when={isDirty()}
message="Areyousureyouwanttoleave?"
/>
...
</form>
);
}

Now if our form is dirty, the Prompt component will warn the user if they try to navigate away from the current route.

Here’s the full example so you can see it in action. Navigate to /contact, “dirty” the form, then try to navigate away.


What I love about React Router is its dedication to declarative, “React like” code. The fundamental flow of a React app is that a user event triggers a state change which then causes a re-render. It’s fitting that the Prompt component follows this same pattern.

Want to learn more?
If you liked this post and want to learn more, check out our free Comprehensive Guide to React Router.

Before you leave

I know, "another newsletter pitch" - but hear me out. Most JavaScript newsletters are terrible. When's the last time you actually looked forward to getting one? Even worse, when's the last time you actually read one rather than just skim it?

We wanted to change that, which is why we created Bytes. The goal was to create a JavaScript newsletter that was both educational and entertaining. 84,338 subscribers and an almost 50% weekly open rate later, it looks like we did it.

Delivered to 84,338 developers every Monday

Avatar for Tyler McGinnis

Tyler McGinnis

CEO of ui.dev. Obsessed with teaching, writing, swimming, biking, and running.

Share this post

Want more react-router?

This is part of our React Router course. You can take the rest of the course by starting a free 3-day trial.