React Router DOM Programmatic Navigation
Learning objective: By the end of this lesson, students will be able to implement useNavigate() to redirect users after an action has been taken.
Understanding programmatic navigation
In web applications, users are often redirected to a different page after completing certain actions, like a form submission. React Router provides this functionality through the useNavigate() hook. With useNavigate(), we can navigate users programmatically, without traditional links.
The useNavigate() hook returns a function that can be used for programmatic navigation:
const navigate = useNavigate();
The navigate function is called upon with a string representing the path to direct the user towards:
navigate('/some-path');
In our application, we’ll demonstrate this by creating a form for adding new Pokémon. After submitting the form, users will be redirected to the /pokemon route, where they can view the list of Pokémon, including the newly added one.
Adding the form component
First let’s create the form.
Create a new component called PokemonForm:
mkdir src/components/PokemonForm
touch src/components/PokemonForm/PokemonForm.jsx
Add the following to PokemonForm.jsx:
// src/components/PokemonForm/PokemonForm.jsx
import { useState } from 'react';
const initialState = {
name: '',
weight: 0,
height: 0,
};
const PokemonForm = (props) => {
const [formData, setFormData] = useState(initialState);
const handleSubmit = (evt) => {
evt.preventDefault();
// TODO : complete submit logic
};
const handleChange = ({ target }) => {
setFormData({ ...formData, [target.name]: target.value });
};
return (
<main>
<h2>New Pokemon</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name:</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
<label htmlFor="weight">Weight:</label>
<input
type="number"
id="weight"
name="weight"
value={formData.weight}
onChange={handleChange}
/>
<label htmlFor="height">Height:</label>
<input
type="number"
id="height"
name="height"
value={formData.height}
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
</main>
);
};
export default PokemonForm;
Note that we’ll finish the handleSubmit function shortly. With our form in place, we should be ready to build some navigation for it.
Adding navigation
Let’s start by updating our nav bar with a link to the new component.
Add the new link to src/components/NavBar/NavBar.jsx:
// src/components/NavBar/NavBar.jsx
<ul>
<li>
<Link to='/'>Home</Link>
</li>
<li>
<Link to='/pokemon'>Pokemon</Link>
</li>
{/* A new link to our pokemon form page */}
<li>
<Link to="/pokemon/new">New Pokemon</Link>
</li>
</ul>
Next, we’ll define a new <Route /> that matches the path as defined in our <Link />.
Add the following import to src/App.jsx:
// src/App.jsx
import PokemonForm from './components/PokemonForm/PokemonForm';
And add the new <Route> to src/App.jsx:
// src/App.jsx
<Routes>
<Route path='/' element={<h2>Home Page</h2>} />
<Route path="/pokemon" element={<PokemonList pokemon={pokemon} />} />
{/* New route to add a pokemon with the Pokemon form */}
<Route path="/pokemon/new" element={<PokemonForm />} />
<Route
path="/pokemon/:pokemonId"
element={<PokemonDetails pokemon={pokemon} />}
/>
<Route path="*" element={<h2>Whoops, nothing here!</h2>} />
</Routes>
You should now be able to navigate to the new pokemon page and enter information in the form.
🧠 Route order does not matter in React Router, but it is easier to maintain your routes in a larger app if you place them in some order. Looking at the order above, we’re using this type of ordering method:
- Exact match to the root path (
/).- Exact match on a specific path (
/pokemon).- Exact match to a specific sub-path on that specific path (
/pokemon/new).- Dynamic route parameters to sub-paths on that specific path (
/pokemon/:pokemonId).- A catch-all route for any other paths (
*).We’ll generally follow an order like this as we construct routes together, but it’s good to remember that this is not a requirement.
Completing the form functionality
Now we can make our form component fully-functional. To do so, we’ll need a new function in src/App.jsx that adds new pokemon data to pokemon array state.
Add the following function to src/App.jsx:
// src/App.jsx
const addPokemon = (newPokemonData) => {
newPokemonData._id = pokemon.length + 1;
setPokemon([...pokemon, newPokemonData]);
};
Notice the _id property being added to newPokemonData before it is included in pokemon state. Here we are giving the new pokemon object a unique identifier based on the current length of pokemon state. This is practical because we aren’t using a database at the moment, and we wouldn’t expect our users to enter a unique id value when filling out the form.
Next, we’ll need to pass the function down to our form component.
Pass the addPokemon function to the PokemonForm component:
// src/App.jsx
<Routes>
<Route path='/' element={<h2>Home Page</h2>} />
<Route path="/pokemon" element={<PokemonList pokemon={pokemon} />} />
{/* Updated route passing PokemonForm the addPokemon prop */}
<Route
path="/pokemon/new"
element={<PokemonForm addPokemon={addPokemon} />}
/>
<Route
path="/pokemon/:pokemonId"
element={<PokemonDetails pokemon={pokemon} />}
/>
<Route path="*" element={<h2>Whoops, nothing here!</h2>} />
</Routes>
And finally, complete the handleSubmit function in the PokemonForm component by calling the new function:
// src/components/PokemonForm/PokemonForm.jsx
const handleSubmit = (evt) => {
evt.preventDefault();
props.addPokemon(formData);
setFormData(initialState);
};
🏆 Notice how we are resetting form state after submission using
initialState. This pattern is great to ensure consistency and prevent bugs.
Programmatic redirects
Time to handle the programmatic redirect. Remember, when a user submits a form, they should be redirected to pokemon list page ('/pokemon'). We’ll be working within PokemonForm.jsx for this step, although this could also be accomplished within App.jsx.
Inside PokemonForm.jsx, import useNavigate from react-router:
// src/components/PokemonForm/PokemonForm.jsx
import { useNavigate } from 'react-router';
Next, create a new instance of useNavigate() inside the component. Add this line right after we set up the formData state variable:
// src/components/PokemonForm/PokemonForm.jsx
const [formData, setFormData] = useState(initialState);
// New code
const navigate = useNavigate();
And finally, call the navigate() function within handleSubmit to redirect the user after state is updated:
// src/components/PokemonForm/PokemonForm.jsx
const handleSubmit = (evt) => {
evt.preventDefault();
props.addPokemon(formData);
setFormData(initialState);
// Navigate to the pokemon list page after submission.
navigate('/pokemon');
};
🚨 Be sure to pass in the path you wish to direct a user to.
Try it out in your browser.
You should now be directed to the pokemon list page after you submit the form. Our application is complete, great job!