React Redux Tutorial: Building Your Own Web App

An introduction into React-Redux — a framework for building web applications. This tutorial is implemented with ES6 and Webpack.

Aug 29, 2017 • Kevin Hou

Introduction

Redux has been making a strong appearance recently in the world of web development. In a nutshell, it is essentially a way of keeping and consolidating props outside of the React components. The goal is to eliminate the need for local states and use the React components for purely rendering and small minor helper functions. The creators of Redux set out to tackle the challenge many developers faced, myself included, of passing properties between many components.

I’ve been working with React since it’s birth for about 3 years now and I’ve been a huge fan ever since. I started using Redux this past summer at Moat (Now Oracle as of August 1st) and it’s been a great addition to my skillset and productivity because of the ways in which it improves large-scale React web apps.

In this post, I will cover the basics of React/Redux as well as its benefits. Redux works by using React components, containers, actions, and reducers. Reducers receive action dispatches and update the Redux state. All Redux state changes must be routed through the reducer. Additionally, all actions must be connected to components through containers. Component can only update the local state by triggering actions. In short, reducers sit on top of the state, actions triggers reducers, containers connect components with states as well as actions, and components display client-facing components.

Dependencies

react
react-dom
react-redux
redux
redux-logger
redux-thunk

Boilerplate Code

1. Entry-Point

// client/src/index.jsimportReactfrom'react';import{render}from'react-dom';import{Provider}from'react-redux';// This imports a redux store that we will get into nextimportstorefrom'./ConfigureStore.js';// I've found it helpful to use a generic entry container that houses subsequent containersimportEntryfrom'./Containers/EntryContainer';// Can call actions on render which can be helpful for fetch requestsimport{someAction}from'./Actions/MainActions';// Import styleguide (optional) that doesn't correspond to a specific componentimport'./Styles/main.scss';// Get styleguidestore.dispatch(fetchRepos());// Fetch the fe-component reposrender(// Must wrap with the provider and store<Providerstore={store}><Entry/></Provider>,
// The target in the DOM you are rendering to// This will be in the client/dist/index.htmldocument.getElementById('main'));

2. Configuring the Store

// client/src/ConfigureStore.jsimport{createStore,applyMiddleware}from'redux';importthunkMiddlewarefrom'redux-thunk';importcreateLoggerfrom'redux-logger';// For debugging in the consoleimportmainReducerfrom'./Reducers';// Import the reducer (client/src/Reducers/index.js)// Thunk middleware is used to allow functions to be passed as actionsconstmiddlewares=[thunkMiddleware];// If debug mode is on, log state changes and actions to the consoleconstdebuggerMode=false;// Can also be determined by process.ENV variablesif(debuggerMode){constloggerMiddleware=createLogger();middlewares.push(loggerMiddleware);// Add middleware}// Creates store that handles the complete state tree of app// This is exported and used by the providerexportdefaultcreateStore(mainReducer,applyMiddleware(...middlewares));

3. Creating a Component

Components are the purely frontend, client-side rendering piece of the puzzle. They should have limited logic and mainly act as pure functions. They are connected to containers which feed them props, but for now we will simply be creating a component with property requirements.

Pure Functional Component (Just Rendering)

// client/src/Components/<folder-name>/<component-name>.jsximportReactfrom'react';importPropTypesfrom'prop-types';// For enforcing prop typesimport'../../Styles/<component-name>.scss';// Import the corresponding stylesheetconstComponentName=(props)=>(<div>{props.message}</div>
);// Declare which props are what type and if they are optionalComponentName.propTypes={message:PropTypes.string.isRequired,someObject:PropTypes.shape({name:PropTypes.string.isRequired,age:PropTypes.number,numbers:PropTypes.arrayOf(PropTypes.number).isRequired,}),// Not required};exportdefaultComponentName;

More Logic-Heavy Component

// client/src/Components/<folder-name>/<component-name>.jsximportReact,{Component}from'react';// Must import the Component class typeimport'../../Styles/<component-name>.scss';// Import sass file for this componentclassLoadingextendsComponent{constructor(props){// Can now use other functionssuper(props);...}randomFunction(input){...}render(){return(<div>{this.props.message}</div>
)}}ComponentName.propTypes={...};exportdefaultComponentName;

4. Action Types

All actions must dispatch an object that dictates to a reducer what it should do. The way the reducer registers what type of logic to carry out is by a unique action.type. These are stored in the Constants/MainActionTypes.js file. It looks something like this:

5. Reducers

Reducers respond to actions when they dispatch a return object. These objects are picked up by the reducers and some logic is carried out that can update the state. Reducer functionality should be kept at a minimum and it is very important that they remain pure-functions — that is, they only rely on input, no other information, for calculating an output. A reducer looks like this:

// client/src/Reducers/SomeReducer.js// This gives you access to the action types we created in step 4import*astypesfrom'../Constants/MainActionTypes';constinitialState={someValue:true,// Default initial state for this variable};constsomeReducer=(state=initialState,action)=>{// The app can decide what to do based on the action type// The return object for this function will be the new stateswitch(action.type){casetypes.SOME_ACTION_TYPE:return{...state,someValue:false,};default:// Default, no state changereturnstate;};returnstate;};exportdefaultsomeReducer;

The reducers are then consolidated into one file in the Reducers/index.js file:

// client/src/Reducers/index.jsimport{combineReducers}from'redux';// Import all the reducersimportsomeReducerfrom'./someReducer';...importanotherReducerfrom'./anotherReducer';// Export for use in the entry index.js fileexportdefaultcombineReducers({// 'name' refers to the name of the reducer you will use to access the variables associated with itname:someReducer,...another:anotherReducer,});

6. Actions

Actions are like any function except that the only thing that really matters is their return object. Actions are dispatched (thanks to redux-thunk) and their return objects are what the reducers receive and interpret; therefore, it is imperitive that the action return has type value associated with it. We will set them using the MainActionTypes.js constants we wrote earlier so that there are no typos. Actions are generally referenced in the container and attached to a component as a prop as we’ll see in step 7.

// client/src/Actions/mainActions.jsimport'whatwg-fetch';// Fetch requestsimport*astypesfrom'../Constants/MainActionTypes';exportconstfetchJSON=()=>{consturl='...';// Returning a promise allows you to use the 'dispatch' function in the child scopereturn(dispatch)=>{dispatch(requestSent(url));// Actions can dispatch other actions// Return the contents of the fetch promisereturnfetch(url,{// See watwg-fetch for docscredentials:'same-origin',// If same origin}).then(response=>response.json())// Parse response.then(json=>{console.log('Received data');dispatch(receivedData(json));returnjson;// Return it as a promise — will be the result of the original action// Example:// this.props.fetchJSON().then((response) { ... }).catch((error) { ...});}).catch(error=>{// Catch any errorsdispatch(actionError(error));});};};

7. Containers

Containers put steps 3-5 together and lets you package the component, reducer, and constants all together. Containers connect the actions, redux state values, and reducers to the components as props. When you want to render the contents of the container, you include the <ContainerName /> as opposed to its child <ComponentName />.

// client/src/Containers/someContainer.jsimport{connect}from'react-redux';// Get the componentimportSomeComponentfrom'../Components/SomeComponent';// Import actions you'd like to make usable to a componentimport{someAction}from'../Actions/MainActions';// Map the redux states to propsconstmapStateToProps=(state)=>({// 'name' is the name of the reducer you specified in step 5 in the file:// client/src/Reducers/index.jssomeValue:state.name.someValue,});// Map actions to propsconstmapDispatchToProps=(dispatch)=>({viewComponent:(input)=>(dispatch(someAction(input))),});// Connects your component to the store using the previously defined functionsexportdefaultconnect(mapStateToProps,// Add the statesmapDispatchToProps// Add the actions)(SomeComponent);// Connect them to the component

Conclusion

If you put all those pieces together you should end up with your first, boilerplate React/Redux application! It’s a really useful tool and a great addition to any stack — especially those that already use React. I will be working with this stack very shortly on a personal project as well as some business projects so I’ll be sure to keep you all updated on what I’ll continue to learn!