How to Transition to Another Route on Successful Async Redux Action
Feb 3, 2020 • 12 Minute Read
Introduction
Redirection in a single-page application can be a bit tricky, especially with React and Redux. We can redirect programmatically in the component itself or a middleware. In this guide, we are going to learn how to redirect when there's a successful async action.
There are several ways to redirect a user to another route, including the history.push() method and the <Redirect /> component from react-router. In this guide, we will use a Redux action to redirect the user using the <Redirect /> component.
We will dispatch an action to redirect a user to the home page after they submit a registration form.
Action Creators and Reducer
Let's begin with action creators and a reducer.
ui.js
In the ui.js file, we will create the REDIRECT action, and in its payload, we will pass the link of the redirect page. I have included a console.log() statement so that it will be more evident when the action gets dispatched.
export const REDIRECT = "REDIRECT";
// action creators
export const redirect = link => {
console.log("=== REDIRECT ACTION DISPATCHED ===");
return { type: REDIRECT, payload: link };
};
register.js
In the register.js file, we will create a REGISTER action, which will be dispatched when the user clicks on the Submit button of the registration form and will send the user data in the payload.
export const REGISTER = "REGISTER";
export const register = user => {
console.log("=== REGISTER ACTION DISPATCHED ===");
return {
type: REGISTER,
payload: user
};
};
api.js
All the actions related to network requests like API_REQUEST, API_SUCCESS, and API_ERROR will be included in the api.js file.
// action types
export const API_REQUEST = "API_REQUEST";
export const API_SUCCESS = "API_SUCCESS";
export const API_ERROR = "API_ERROR";
export const CANCEL_API_REQUEST = "CANCEL_API_REQUEST";
// action creators
export const apiRequest = ({ url, method, data }) => {
return {
type: API_REQUEST,
meta: { url, method, data }
};
};
export const cancelApiRequest = () => {
return {
type: CANCEL_API_REQUEST
};
};
export const apiSuccess = ({ response }) => ({
type: API_SUCCESS,
payload: response
});
export const apiError = ({ error }) => ({
type: API_ERROR,
payload: error
});
reducer.js
The reducer will be reasonably simple; we will store the redirect link in the store. We don't need to add the user's data here, as it's not going to be used in any component of our application.
const reducer = (state = {}, action) => {
switch (action.type) {
case REDIRECT:
return { redirectTo: action.payload };
default:
return state;
}
};
export default reducer;
In the next section, we will start writing the registration form.
The Registration Form
In the registration form, we will have three fields: name, email, and password. To make the UI beautiful and clean, I'll be using material-ui, but you can use any UI library of your choice.
// ..
<form>
<Typography variant="h5" style={{ marginBottom: 8 }}>
Create an account
</Typography>
<TextField
label="Name"
variant="outlined"
fullWidth
className="form-input"
value={name}
onChange={e => setName(e.target.value)}
/>
<TextField
label="Email"
variant="outlined"
fullWidth
className="form-input"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<TextField
label="Password"
variant="outlined"
fullWidth
className="form-input"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<Button
variant="contained"
color="primary"
fullWidth
className="form-input"
size="large"
onClick={submitForm}
>
Register
</Button>
{(props.error || error) && (
<Alert severity="error" onClick={() => setError(null)}>
{props.error || error}
</Alert>
)}
</form>
// ..
We'll create the form as a functional component and manage the state using the useState hook.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [error, setError] = useState("");
On Submit button click, we will do basic validations and dispatch the REGISTER action.
const submitForm = () => {
if (email === "" || password === "" || name === "") {
setError("Fields are required");
return;
}
props.register({ name, email, password });
};
From the registration page itself, we will redirect the user to the home page. To do that, we will check if we have the redirectTo property in the global state, and based on that redirect, the user using the <Redirect /> component from react-router.
if (props.redirectTo) {
return <Redirect to={props.redirectTo} />;
}
So finally, our registration page will be as follows :
import React, { useState } from "react";
import { TextField, Typography, Button } from "@material-ui/core";
import { connect } from "react-redux";
import { register } from "../actions/register";
import MuiAlert from "@material-ui/lab/Alert";
import { Redirect } from "react-router";
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
export default connect(({ redirectTo }) => ({ redirectTo }), { register })(
props => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [name, setName] = useState("");
const [error, setError] = useState("");
const submitForm = () => {
if (email === "" || password === "" || name === "") {
setError("Fields are required");
return;
}
props.register({ email, password });
};
if (props.redirectTo) {
return <Redirect to={props.redirectTo} />;
}
return (
<form>
<Typography variant="h5" style={{ marginBottom: 8 }}>
Create an account
</Typography>
<TextField
label="Name"
variant="outlined"
fullWidth
className="form-input"
value={name}
onChange={e => setName(e.target.value)}
/>
<TextField
label="Email"
variant="outlined"
fullWidth
className="form-input"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<TextField
label="Password"
variant="outlined"
fullWidth
className="form-input"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<Button
variant="contained"
color="primary"
fullWidth
className="form-input"
size="large"
onClick={submitForm}
>
Register
</Button>
{(props.error || error) && (
<Alert severity="error" onClick={() => setError(null)}>
{props.error || error}
</Alert>
)}
</form>
);
}
);
Redux Middlewares
As in this earlier guide, we are going to have two middleware functions. The first will handle network requests, and the second will handle app-specific requirements—in our case, registration.
app.js
In the app.js file, we will catch the REGISTER action, dispatch the API_REQUEST action using the apiRequest() action creator, and pass the user data into it.
import { apiRequest } from "../actions/api";
import { REGISTER } from "../actions/register";
const SERVER_URL = `https://61m46.sse.codesandbox.io`;
export const appMiddleware = () => next => action => {
next(action);
switch (action.type) {
case REGISTER: {
next(
apiRequest({
url: `${SERVER_URL}/register`,
method: "POST",
data: action.payload
})
);
break;
}
default:
break;
}
};
core.js
In the core.js file, we will make the network request to post the registered user data to the server. If the request is successful, we will dispatch the API_SUCCESS action with the response in the payload and also at the same time dispatch the REDIRECT action with the redirect link.
import axios from "axios";
import { API_REQUEST, apiError, apiSuccess } from "../actions/api";
import { redirect } from "../actions/ui";
export const apiMiddleware = ({ dispatch }) => next => action => {
next(action);
if (action.type === API_REQUEST) {
const { url, method, data } = action.meta;
axios({
method,
url,
data
})
.then(({ data }) => {
dispatch(apiSuccess({ response: data }));
dispatch(redirect("/home"));
})
.catch(error => {
console.log(error);
dispatch(apiError({ error: error.response.data }));
});
}
};
Main App Component
In the App.js file, we will wrap the components with the <Provider /> so that the Redux store is available to all the components in the application. We will have three routes or pages: the index page, which will be displayed initially; the registration page, which will be displayed when the user clicks on the Register button, and the home page, where the user will be redirected after a successful registration.
App.js
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import "./app.css";
import { Provider } from "react-redux";
import { applyMiddleware } from "redux";
import { createStore } from "redux";
import NavBar from "./components/Nav";
import { Typography, Divider } from "@material-ui/core";
import HomePage from "./pages/HomePage";
import RegisterPage from "./pages/Register";
import { appMiddleware } from "./middlewares/app";
import { apiMiddleware } from "./middlewares/core";
import reducer from "./reducer";
const createStoreWithMiddleware = applyMiddleware(
appMiddleware,
apiMiddleware
)(createStore);
const store = createStoreWithMiddleware(reducer);
const IndexPage = () => (
<>
<Typography variant="h3">Welcome to the App</Typography>
<Divider style={{ marginTop: 10, marginBottom: 10 }} />
<Typography variant="h6">Feel free to take a look around</Typography>
</>
);
export default function App() {
return (
<Provider store={store}>
<Router>
<NavBar />
<div className="container">
<Switch>
<Route path="/register">
<RegisterPage />
</Route>
<Route path="/home" render={HomePage} />
<Route path="/" render={IndexPage} />
</Switch>
</div>
</Router>
</Provider>
);
}
Conclusion
Using middlewares to redirect to other routes or pages is a relatively new concept, but it's a very scalable solution for robust and complex applications. Middlewares can also be used for reporting bugs, crash reports, logging data, etc. You must understand how middlewares work to write maintainable code without a third-party library, hence keeping the source code minimal and lightweight.
Do let me know if you have any other solutions to redirect a user by contacting me at CodeAlphabet.
That's it from this guide. Hope you are having a good time coding like a beast.