Updated:
I have created a live coding version for this tutorial. Check it at here: https://youtu.be/MdeqXWo4fOI
Login is usually the first page we have to build in almost any app. In this tutorial, we will build a interactive and intuitive login form with React & Redux.
Here are some features of our login form:
- It has an email field and a password
- When user clicks submit, it will show a message to indicate that it is sending request to server.
- When receiving response, it will show login status.

An ugly wild login form appears 🙂
Contents
Prerequisite libraries
- create-react-app: I always use this library to create my React project, it will install React and some scripts with make our React app running
- redux: our state management
- redux-thunk: we use this library to compose some actions and create flow for login
- redux-logger: this library logs every changes to Redux store
Application state
Because we only have a login form, our application state is pretty simple, it has 3 attributes:
-
isLoginPending: indicates when login has sent login request
-
isLoginSuccess: indicates if login successful
-
loginError: contains the error message if login fail
Let’s create some actions for these attributes
const SET_LOGIN_PENDING = 'SET_LOGIN_PENDING'; const SET_LOGIN_SUCCESS = 'SET_LOGIN_SUCCESS'; const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR'; function setLoginPending(isLoginPending) { return { type: SET_LOGIN_PENDING, isLoginPending }; } function setLoginSuccess(isLoginSuccess) { return { type: SET_LOGIN_SUCCESS, isLoginSuccess }; } function setLoginError(loginError) { return { type: SET_LOGIN_ERROR, loginError } }
Simulating login request
Here is the source code to simulating our login request. We provide email, password and a callback. If email and password are correct, it will call the callback and pass a null parameter. Otherwise, it will pass an error object.
function callLoginApi(email, password, callback) { setTimeout(() => { if (email === 'admin@example.com' && password === 'admin') { return callback(null); } else { return callback(new Error('Invalid email and password')); } }, 1000); }
Compose actions together
Now we can write an action to actual send login request and dispatch our actions!
export function login(email, password) { return dispatch => { dispatch(setLoginPending(true)); dispatch(setLoginSuccess(false)); dispatch(setLoginError(null)); callLoginApi(email, password, error => { dispatch(setLoginPending(false)); if (!error) { dispatch(setLoginSuccess(true)); } else { dispatch(setLoginError(error)); } }); } }
So when this action is dispatched, it set login pending status to true, login success to and login error to null. After that it calls our simulated login api.
The reducer
Here is the function that actually makes change to our Redux store
export default function reducer(state = { isLoginSuccess: false, isLoginPending: false, loginError: null }, action) { switch (action.type) { case SET_LOGIN_PENDING: return Object.assign({}, state, { isLoginPending: action.isLoginPending }); case SET_LOGIN_SUCCESS: return Object.assign({}, state, { isLoginSuccess: action.isLoginSuccess }); case SET_LOGIN_ERROR: return Object.assign({}, state, { loginError: action.loginError }); default: return state; } }
Nothing is complicated at here
UI component
Here is the login form. In practice, we should separate this component into smart and dump components. But for our tutorial, this is enough.
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { login } from '../../redux/reducer'; import './LoginForm.css'; class LoginForm extends Component { constructor(props) { super(props); this.state = {}; this.onSubmit = this.onSubmit.bind(this); } render() { let {email, password} = this.state; let {isLoginPending, isLoginSuccess, loginError} = this.props; return ( <form name="loginForm" onSubmit={this.onSubmit}> <div className="form-group-collection"> <div className="form-group"> <label>Email</label> <input type="email" name="email" onChange={e => this.setState({email: e.target.value})} value={email}/> </div> <div className="form-group"> <label>Password</label> <input type="password" name="password" onChange={e => this.setState({password: e.target.value})} value={password}/> </div> </div> <input type="submit" value="Login" /> { isLoginPending && <div>Please wait...</div> } { isLoginSuccess && <div>Success.</div> } { loginError && <div>{loginError.message}</div> } </form> ) } onSubmit(e) { e.preventDefault(); let { email, password } = this.state; this.props.login(email, password); this.setState({ email: '', password: '' }); } } const mapStateToProps = (state) => { return { isLoginPending: state.isLoginPending, isLoginSuccess: state.isLoginSuccess, loginError: state.loginError }; } const mapDispatchToProps = (dispatch) => { return { login: (email, password) => dispatch(login(email, password)) }; } export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
In this component, we connect our application to Redux store. Mapping isLoginPending, isLoginSuccess and loginError to the form. These attributes will indicate form status.
When form is submitted, it takes email and password from from and dispatchs the login action.
That’s our login form with React and Redux. In real app, you usually do something more like: store login token to Redux store after login success or redirect to homepage. I guess these things are too easy for you now 🙂
You can grab the source code at here: https://github.com/davidtran/react-redux-login-flow
Let me know if you have any question.