Check your version
This post assumes you're using React Router v5. 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 little UX details. One of the most frustrating experiences a user can have is when they spend time inputting data into your app, accidentally hit a hotkey to navigate away from the current route, then have to restart the form all over. There are a few different approaches to fixing this bad UX but in this post, we'll focus on how React Router v5 can help by warning before a route transition.
Before we can see how it's done, we'll need the basic skeleton of an app. Our app will be pretty straight forward. It will have a few different routes - one of which will render a form.
import * as React from "react";import { BrowserRouter as Router, Route, Link } from "react-router-dom";function Form() {return <form></form>;}export default function App() {return (<Router><div><ul><li><Link to="/">Form</Link></li><li><Link to="/one">One</Link></li><li><Link to="/two">Two</Link></li></ul><Route path="/" exact><Form /></Route><Route path="/one"><h3>One</h3></Route><Route path="/two"><h3>Two</h3></Route></div></Router>);}
Now the goal is to make it so if the form is "dirty" and the user tries to navigate away, we verify that's what they're really trying to do.
What I love about React Router v5 is its dedication to declarative, "React like" code. The fundamental aspect of React is user event -> state change -> re-render
. With that in mind, it would make sense that the "React way" of preventing transitions follows that same paradigm - a declarative component we can render (or not) based off of the component's state.
First, as mentioned, let's add some state to the Form
component. The goal here is to have some state which knows if the form is "dirty" (meaning the length of the value in the input field is > 0).
function Form() {const [isBlocking, setIsBlocking] = React.useState(false);return (<form><p>Blocking? {isBlocking ? "Yes, click a link or the back button" : "Nope"}</p><p><inputsize="50"placeholder="type something to block transitions"onChange={(event) => setIsBlocking(event.target.value.length > 0)}/></p></form>);}
Now we have a property on our state, isBlocking
, which tells us if we should warn the user before they transition away from the current route.
Next, we need to actually make it so the user is prompted if they try to navigate away and isBlocking
is true. Conveniently, React Router v5 comes with a Prompt
component that serves this exact purpose. It takes in two props - when
and message
. when
needs to be a boolean that when true
, will show the user a prompt with the message
prop when they try to navigate away.
We've already added an isBlocking
property to our state, so we can pass that to Prompt
s when
prop.
...import {Prompt,} from 'react-router-dom'...function Form () {const [isBlocking, setIsBlocking] = React.useState(false)return (<form><Promptwhen={isBlocking}message={(location) => `Are you sure you want to go to ${location.pathname}`}/><p>Blocking? {isBlocking? 'Yes, click a link or the back button': 'Nope'}</p><p><inputsize="50"placeholder="type something to block transitions"onChange={(event) => setIsBlocking(event.target.value.length > 0)}/></p></form>)}
Now if the input field if "dirty", the Prompt
component will warn the user if they try to navigate away from the current route.
Lastly, let's finish the example by actually adding a button
to our form and resetting isBlocking
when the form is submitted.
function Form() {const [isBlocking, setIsBlocking] = React.useState(false);return (<formonSubmit={(e) => {e.preventDefault();e.target.reset();setIsBlocking(false);}}><Promptwhen={isBlocking}message={(location) =>`Are you sure you want to go to ${location.pathname}`}/><p>Blocking? {isBlocking ? "Yes, click a link or the back button" : "Nope"}</p><inputsize="50"placeholder="type something to block transitions"onChange={(event) => setIsBlocking(event.target.value.length > 0)}/><br /><button type="submit">Submit to stop blocking</button></form>);}