It’s Time for a React & Redux Tutorial

By Amitai B.

Nov 18, 2016

React

What is it? React is a JavaScript (JS) library for rendering web pages. That’s it. The closest equivalent is Angular with only directives, and without the other features such as services, controllers, etc.

So why use it?

  • React is fast. It renders the pages using virtual DOM, instead of rendering to the actual DOM. It uses the virtual DOM to compare the changes of the page and then renders to the DOM only the changes.
  • It forces the developer to use the best software development practices, such as unidirectional data flow.
  • React native – React Development and React Native are very similar, and the latter is an excellent way to create mobile native apps.
  • It allows server rendering.
  • Once you understand the principles, it is very easy to write complex web applications.
  • React has huge ecosystem with libraries like Redux, GraphQL, Relay and more that they are complementary technologies to help make a great development process.

The React way – 4 principles of developing React applications:

  1. Break the UI down to components, and each component down to smaller components.
  2. Keep unidirectional data flow – from the top component to the children by using props.
  3. Identify the UI state.
  4. Dispatch actions to change the state.

Build a React application

The best way to learn React is to do it with an example. Toward that end, I will build a simple application that greets the user. I will do it step by step, with each step demonstrating one or more methods and principles. Although it is very small tutorial, it is enough to understand React concepts and how to expand them into a real application.

Building the app

I will use the new “create-react-app” that removes all the boiler plates of configuring, packaging and serving the app (Yes, it’s a pain!).

Install create-react-app, create the app:

npm install -g create-react-app
create-react-app HelloApp
cd HelloApp
npm start

Now, we can start implementing our shiny HelloApp. The final app will have two screens: one for the login, and the other to greet the user that is logged in.

Write the first component

In the folder tree, we will create a components folder to contain all the component files. We will also use sub folders for each page. The component name and filename will begin with a capital letter (as is conventional). Top components that display the whole page will end with “Page”:

// src/components/hello/HelloPage.js
import React, {Component} from 'react';

class HelloPage extends Component {
    render() {
        return (
            <div>
            <h1>Hello</h1>
            </div>
            );
     }
}

export default HelloPage;

HelloPage is a class that is derived from React.component. It must have a render method that returns the graphic markup of the component in JSX, which is a language very similar to HTML.

JSX

JSX is a syntactic sugar for function calls and object construction. Basically, it let us write the code easily and makes the code more understandable.

Instead of this:

React.createElement(
a,
{href:"spectory.com"},
'Spectory website’'
)

We can write:

<a href=”spectory.com”>Spectory website</a>

It is very similar to HTML, and it lets us integrate JS code. We can change our component to be more dynamic by adding a parameter to it by surrounding it with curly braces:

import React, {Component} from 'react';

class HelloPage extends Component {
    render() {
         let userName = 'amitai';
         return (
         <div>
         <h1>Hello, {userName}, today is {(new Date()).toDateString()}</h1>
         </div>
          );
    }
}

export default HelloPage;

In order to view our new component we need to make some changes in App.js:

import React, { Component } from 'react';
import './App.css';
import HelloPage from './components/hello/HelloPage';

class App extends Component {
    render() {
        return (
            <div >
            <HelloPage />
            </div>
        );
    }
}

export default App;

I replaced the previous content with the new HelloPage component.

The way React renders all the component is through the react-dom library, with the following method:

ReactDOM.render(
<App />,
document.getElementById('root')
);

Once React-DOM was part of React, but because today we can use React in environments without DOM, such as React Native, or even Node (some do it), the React team separated it from its library.

So our HelloPage is ready. But before we will build the login page, we will need a router to navigate between these two pages. I will use the react-router. Let’s install it:

npm install --save react-router

Please note: the version of the package should be v2.0.0

Then we will import and use it in our App component:

// src/app.js
import React, { Component } from 'react';
import { BrowserHistory , Router, Route} from 'react-router'
import './App.css';
import HelloPage from './components/hello/HelloPage';
import LoginPage from './components/login/LoginPage';

class App extends Component {
    render() {
        return (
            <div>
            <Router history={browserHistory}>
            <Route path='/' component={LoginPage} />
            <Route path='/hello' component={HelloPage} />  
            </Router>
            </div>
        );
    }
}

export default App;

The Router uses BrowserRouter to navigate between pages. Its children are the routes, and one should note that every route has a path and component.

I will create a skeleton for our login page:

// src/components/login/LoginPage
import React, {Component} from 'react';

class LoginPage extends Component {
    render() {
        return (
            <div>
            <h1>Login</h1>
            </div>
        );
    }
}

export default LoginPage;

And now if I go to ‘/’ I get the login page. And when I go to ‘/hello’ I get the hello page.

The login page

Now, it is time to build a little more complex component for our login page. As I said before, the way to build it is to view the mockup and split into smaller components:

Login page:

  • Header
  • Form – (inside the form there are two types of components)
    • InputField – for username and password,
    • Button

We will build the component from top to bottom. First, start with the login page:

// src/components/login/LoginPage
import React, {Component} from 'react';

import LoginHeader from './LoginHeader';
import LoginForm from './LoginForm';

class LoginPage extends Component {
    render() {
        return (
            <div>
            <LoginHeader />
            <LoginForm />
            </div>
        );
    }
}

export default LoginPage;

Then we will create the login header:

// src/components/login/LoginHeader
import React from 'react';

const LoginHeader = () => {
    return (
        <div>
        <h1>Login</h1>
        </div>
    );
};

export default LoginHeader;

What is going on? This doesn’t look like a react component at all!

Dumb & smart components

That’s right but this is a React component. There are two types of React components: dumb and smart (or Presentational and Container Components as Dan Abramov, the creator of Redux, calls them).
The smart component is state awareness (stateful); it will be connected to Redux, and has component life-cycle methods (which we will touch on later). The dumb components are only used to display the UI elements. They get their props from their parent and they are not aware of the state (stateless).

As a rule, the default type of the component will be “dumb” unless there is a need to upgrade it. As you can see, a dumb component is a simple function.

Now, we will build the LoginForm. Because this component should activate actions (login), it will be a smart component:

// src/components/login/LoginForm
import React, {Component} from 'react';


import InputField from '../common/InputField';
import SubmitButton from '../common/SubmitButton';

class LoginForm extends Component {
    render() {
        return (
            <form>
            <InputField name="username" />
            <InputField name="password" />
            <SubmitButton value="login" />
            </form>
        );
    }
}

export default LoginForm;

As you can see, both Button and InputField are not in the login folder, but in the common components folder. We will put all the shared components in this folder.

I will create them as dumb components:

// src/components/common/InputField.js
import React from 'react';

const InputField = (props) => {
    return (
        <div className="form-group">
        <label className="control-label">{props.name}</label>
        <input className="form-control" type="text" onChange={props.onChange} />
        </div>
    );
};

export default InputField;
// src/components/common/SubmitButton.js
import React from 'react';

const SubmitButton = (props) => {
    return (
        <div>
        <input className="btn btn-primary btn-lg" type="submit" value={props.value} />
        </div>
    );
};

export default SubmitButton;

I added a little bootstrap and now we have a beautiful login page.

Adding functionality

The application we built so far looks fine but it doesn’t do anything. As I wrote in the beginning, React is only a rendering library, so we need something else to add the functionality. It can be vanilla JS, frameworks like Relay, Meteor or even Angular, but the common usage is with Flux, especially with Redux.

Redux

Redux is a set of libraries that have simplified Flux. It is the most popular library to use with React. I will not describe the Flux architecture, only the way Redux implements it.

Redux is a library that helps maintain the application state. It uses three types of objects:

  1. Actions – An action is a function that the application uses to send data to the store. Every action has a type, which is used by the reducer to change the state.
  2. Reducers – These take the data from the action and insert it into the state.
  3. Store – The store has a few responsibilities:
    • It holds the state of the application, and exposes it.
    • It dispatches actions.
    • It subscribes listeners on state changes events.

Redux data flow

Let’s view the login process to explain the Redux data flow:

  1. The user hits the ‘Login’ button.
  2. This dispatches a login action.
  3. The login action sets the result of the login (the user data)
  4. The reducer collects the data and stores it in the state.
  5. The store notifies the listeners (the components).
  6. The properties of the components change and the display updates.

Redux with React

Redux offers the ‘react-redux’ library that provides functionality to integrate react components with Redux store and actions. We will see how to use them in our application.

Async actions

Many of our operations in our apps are asynchronous. For instance: ajax calls, working with local storage, GPS and more. Redux offers the ‘redux-thunk’ library to implement async actions. It uses an action to dispatch another action(s) when it returns from the async call.

Adding Redux to our application:

Let’s see how to add Redux and make our app dynamic. The action we will implement is the login. It is an async action because it needs to make an ajax call.

Using mock API

A good practice for development UI is using mock services. In this way, we can skip errors in the server, and focus on the UI. I will add a mock login that we will use in this app:

// src/api/mockApi.js
import _ from 'lodash';

const users = [
    {
        id: 1,
        username: '[email protected]',
        password: 'qwe123',
    }
];

export function authenticate(username, password) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
        let user = _.find(users, user => {
        return user.username === username;
    });
    if(user && user.password === password) {
        resolve(Object.assign({}, user));
    }
    else reject(`Wrong login credentials`);
        }, 500);
    });
}

This mock login has one function (authenticate) that returns a promise. It checks against a static list if the given credentials are valid or not and resolves or rejects them accordingly. Notice that I added a short delay to simulate a round trip to the server.

Before doing this, we need to install Redux libraries:

npm install --save redux react-redux redux-thunk

Let’s write our login action:

// src/action/loginActions.js
import * as actionTypes from './actionTypes'; 
import { browserHistory } from 'react-router';
import { authenticate } from '../api/mockApi';

export function login_success(user) {
    return {
        type: actionTypes.LOGIN_SUCCESS,
        user
    }
}

export function login_error(err) {
    return {
        type: actionTypes.LOGIN_ERROR,
        err
    }
}

export function login({username, password}) {
    return (dispatch) => {
        return authenticate(username, password)
    .then((user) => {
        dispatch(login_success(user));
        browserHistory.push('/hello');
    })
    .catch((err) => {
        dispatch(login_error(err));
        })
    }
}

We have here three actions: login, login_success, and login_error.
The login action makes the call to the API and when the result returns, it dispatches either login_success or login_error. In case of success, we will navigate to the ‘/hello’ route that we defined earlier.

It is a good practice to write all the actions types in a separate file, and not use them as strings.

// src/actions/actionTypes.js
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';

The reducer

// src/reducers/login_reducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';

export default function loginReducer(state = initialState.login, action) {
    switch (action.type) {
        case types.LOGIN_ERROR:
            return Object.assign({}, state, { username: '' });
        case types.LOGIN_SUCCESS:
            return Object.assign({}, state, { username: action.user.username });
        default:
            return state;
    }
}

We can see here some Redux rules:

  • The reducer is a pure function that receives action and a state and returns a new state.
  • Any action has a branch in the switch case that manipulates the state.
  • The state is immutable. This means that instead of update to the state, we create a new one. The best way is to use ES6 object.assign. Redux compares the address of the states in order to update the store, unless we will create a new object, the store and the components will not be updated. It lets Redux perform much faster than deep comparing objects.
  • All actions are calling all reducers. This means that we have to define a default that will return the state.
  • We can use a default parameter in order to initiate our state. It is better to do it in a different file where we can see the whole initial state and also its structure. The application state structure is very important. It should be designed in thought to serve the application functionality:
    • Before you add object to the state, stop, and think.
    • Keep it small and as simple as possible.
    • Remember that you can manipulate this state before transferring it to the props (will see that in a minute).
    • Do not keep duplications or redundant data.
    • Treat it like you treat the database tables. It will create for you a lot of headaches in the future.

The application state

We have only two things in the application state:

  • The user
  • Login errors

This is the initial state:

export default {
    login: {
        user: null,
        error: '',
    }
}

If we are working with one reducer we can add it to the store directly, but it is much better to divide it to multiple reducers. Every one of them is then responsible for a slice of the state.

We can use Redux “combineReducers” to do that:

// src/reducers/index.js
import { combineReducers } from 'redux';
import login from './login_reducer';

export default rootRecucer = combineReducers({
    login
});

Notice that the name of the slice will be “login” (as the name we put in the combineReducer function).

The Redux store

Now, we need to create a store and connect it to the reducers on the one hand and to the components on the other hand.

// src/app.js
import React, { Component } from 'react';
import { BrowserHistory , Router, Route} from 'react-router'
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import './App.css';
import HelloPage from './components/hello/HelloPage';
import LoginPage from './components/login/LoginPage';

const store = createStore(
    rootReducer,
    applyMiddleware(
        thunk
    )
)

class App extends Component {
    render() {
        return (
            <Provider store={store}>
            <div className="container">
            <Router history={browserHistory}>
            <Route path='/' component={LoginPage} />
            <Route path='/hello' component={HelloPage} />
            </Router>
            </div>
            </Provider>
        );
    }
}

export default App;

A lot is going on here:

  • We create a store, and give it the rootReducer that we created earlier. The second argument is the middleware you want to integrate to the store, in our case it is only Redux-thunk, but we can add more if we like. This is the connection to the reducers.
  • Next we will connect the store to the components by wrapping the whole application with Provider from React-redux. This will inject the store to all the children of the components.

This is good time to explain React components’ children. “Children” is a unique prop in React; it is the only one that you do not need to specify. It is created automatically when the component has child elements. It lets the parent component change the state or the context of its children (similar to ng-transclude). In this way, the Provider component or the Router works.

Get the state and action to the components

The last thing we need to add is the part where the components receive the state and actions, We will use React-redux for that:

// src/components/LoginForm.js
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as loginActions from '../../actions/loginActions';


import InputField from '../common/InputField';
import SubmitButton from '../common/SubmitButton';


class LoginForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            username: '',
            password: ''
        }
    }

    onChangeUser = (e) => {
        this.setState({ ...this.state, username: e.target.value });
    }

    onChangePassword = (e) => {
        this.setState({ ...this.state, password: e.target.value });
    }

    onSubmit = (e) => {
        e.preventDefault();
        this.props.actions.login(this.state);
    }
    render() {
        return (
            <form onSubmit={this.onSubmit}>
            {this.props.login.error === '' ? '' : <div className="alert alert-danger">{this.props.login.error}</div>}
            <InputField name="email" onChange={this.onChangeUser} type="text"/>
            <InputField name="password" onChange={this.onChangePassword} type="password" />
            <SubmitButton value="Login" />
            </form>
        );
    }
}

function mapStateToProps(state) {
    return {
        login: state.login
    }
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(loginActions, dispatch)
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);

In order to bind the store to the component we used “connect” from React-redux. It gets two functions as parameters:

  • The first (mandatory) is the mapStateToProps (This is conventional, and can be named differently). This function gets the state (and the component props) slice, the relevant part from it, and sets the property in the component (in this example – login). This is the place to manipulate the state, or make a small calculation in order to adjust the state to the component props.
  • The second ‘mapDispatchToProps’ binds the actions. Notice that we use bindActionCreators so we can dispatch the actions inside the component as it was a regular function.

I also added state in the component. The state of the component is local, and I can set it with setState. As a rule we do not want to use setState, but to use the Redux global state. The only exceptions are when the state is local and is not related to the application. Here this involves just the typing of the username/password, and changing them is local as long as the user didn’t submit the form.

Once the user submits the form, the function dispatches the login action that was bound to its props.

The last thing we have to do is to change the hello component to receive the name from the login state:

import React, {Component} from 'react';
import { connect } from 'react-redux';

class HelloPage extends Component {
    render() {
        return (
            <div>
            <h1>Hello, {this.props.login.username}, today is {(new Date()).toDateString()}</h1>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        login: state.login
    }
}

export default connect(mapStateToProps)(HelloPage);

This time it is easier because we do not have actions, only the state.

Debugging tools:

React and Redux have great chrome extensions that help us debug our apps.

React Developer Tools

  • View the component and its children.
  • View the state and props of the component.
  • View the component’s events.

Redux DevTools:

  • A must have for developing in Redux
  • View actions
  • View state (of the application, not only the component)
  • View diff of state

To use this, we need to update the code a little bit:

import React, { Component } from 'react';
import { BrowserHistory , Router, Route} from 'react-router'
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import './App.css';
import HelloPage from './components/hello/HelloPage';
import LoginPage from './components/login/LoginPage';

const store = createStore(
    rootReducer,
    composeWithDevTools(applyMiddleware(
    thunk
    ))
)

class App extends Component {
    render() {
        return (
            <Provider store={store}>
            <div className="container">
            <Router history={browserHistory}>
            <Route path='/' component={LoginPage} />
            <Route path='/hello' component={HelloPage} />
            </Router>
            </div>
            </Provider>
        );
    }
}

export default App;

The code for this tutorial can be found here.

Conclusion

React is the most popular framework (with its add-ons) right now. It’s easy to learn and master. Every web developer should learn IMO because it is here to stay. It is back by a large company that pushes to improve it all the time. It is also expanding to any type of UX.
It helps the developer write better software and become more professional.

References

Leave a Reply

Your email address will not be published.