Skip to content

Instantly share code, notes, and snippets.

@baniol
Last active January 16, 2019 08:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baniol/de64830114db9fa700ba9eaeb149cb26 to your computer and use it in GitHub Desktop.
Save baniol/de64830114db9fa700ba9eaeb149cb26 to your computer and use it in GitHub Desktop.
React-Redux general architecture

React-Redux Walkthrough

Introduction to the series

The purpose of this series is to provide a reader with an insight into the data flow in Redux applications and how React components fit into it. To illustrate the Redux architecture we will build a simple application - a list of employees filterable by position.

Each part of the series is going to have a separate branch in the repository.

We'll start with a simple static data collection, progressively adding features and tools such as unit tests, async API calls, debugging tools routing and middleware.

ECMAScript 6 and WebPack module bundler are not going to be discussed in details, you can find many references in the web.

To start off with the project I've used Dan Abramov's (the creator of Redux) boilerplate: https://github.com/gaearon/react-hot-boilerplate

Part 1 - Redux overview

Before proceeding with the theory, let's run the code locally: clone the repository, check out to the 01-introduction branch and run:

npm install
npm start

Then point your browser to http://localhost:3000

Overview

In short, Redux is a library which uses a handful of methods to manage the application state. It can (but doesn't have to) be used with React. For this purpose we'll use a library react-redux - the official bindings for Redux.

The Redux building blocks are:

  • Reducers
  • Store
  • Actions

I'm going to briefly describe the above elements, using our application as the example. The datailed description can be found at Redux creator's page: http://redux.js.org/.

The below diagram shows the organisation of Redux element and the general data flow:

data flow

Redux Data Flow

The bootstrap file is index.js. Here the store object is created with reducers function as a parameter and passed down to React components. Normally, for a React component, you would see something like ReactDOM.render(<App />, document.getElementById('root')), which renders the App component and mounts it in the DOM element. However, Redux comes with a root Provider component that wraps the App component and allows passing the store object to its children components. The store object holds the application state and can be updated with dispatch(action).

In order to bind the state to components Redux uses connect wrapper. connect takes two function arguments:

  • mapStateToProps(state)
  • mapDispatchToProps(dispatch) - not mandatory

The state gets transferred into a component's props with the mapStateToProps method, while the mapDispatchToProps passes functions to a component's props. These functions can be called in components with events like onClick dispatching action creators (functions that returns actions with type and payload) which in turn modify the application state.

Let's take the example of our PositionFilter component.

  1. Mapping state to props:
const mapStateToProps = (state) => {
  return {
    positions: positionList,
    currentFilter: state.positionFilter
  }
}

The keys of the returned objects correspond to the prop names while the values of the object are the properties of the application state tree. positions is an array with position names and currentFilter holds the currently selected position. They are used in the component's render function:

render() {
    let spanStyle = (pos) => {
      let s = {
        marginRight: 10,
        cursor: 'pointer'
      }
      if (pos === this.props.currentFilter) {
        s['backgroundColor'] = 'grey'
      }
      // @TODO ugly - refactor
      return s
    }
    return (
      <div>
        {this.props.positions.map(position =>
          <span
            key={position}
            style={spanStyle(position)}
            onClick={this.props.filterPositions.bind(null, position)}
            >
            {position}
          </span>
        )}
      </div>
    )
  }
  1. Mapping dispatch to props mapDispatchToProps method takes dispatch as a parameter which is used to dispatch a proper action creator.
const mapDispatchToProps = (dispatch) => {
  return {
    filterPositions: (name) => dispatch(setPositionFilter(name))
  }
}

Here, we are passing to the component's props a function filterPositions which takes a position name as a parameter. In the component, the function is bound to onClick event: onClick={this.props.filterPositions.bind(null, position)}. When a user clicks on the position filter the filterPositions method dispatches setPositionFilter action creator (actions/index.js). See the diagram above for a general flow.

What the positionFilter component returns is a 'raw' React component bound to the application state via mapStateToProps and mapDispatchToProps method using connect wrapper: export default connect(mapStateToProps, mapDispatchToProps)(PositionFilter).

More on Reducers

As stated in the official documentation:

The reducer is a pure function that takes the previous state and an action, and returns the next state.

The signature of a reducer is (previousState, action) => newState.

Our simple positionFilter reducer provides only one function:

export default (state=null, action) => {
  switch(action.type) {
    case 'SET_POSITION_FILTER':
      if (!state || state !== action.name) {
        return action.name
      } else {
        return null
      }
    default:
      return state
  }
}

It sets the initial state as null. When the action SET_POSITION_FILTER is dispatched, by clicking the position filter in the PositionFilter component, the positionFilter reducer is called. It takes the previous state (first arguent) and compares it to the current selected (action.name) to determine if the filter should be switch on or off.

Our other reducer, employees.js returns the initial state which is the employees collection (we will add cases in the upcoming parts)

export default (state=employeeList, action) => {
  switch(action.type) {
    default:
      return state
  }
}

NOTE: positions should have their own reducer, with the initial state and not being manually passed to mapStateToProps. We will correct this in the future.

The combineReducers function (reducers/index.js file) combines the two reducers into a single state object in form of separate properties:

export default combineReducers({
  employees,
  positionFilter
})

(Note the ES6 object literal shorthand)

The above means that the employees state will be managed (updated) by the employees reducer and the positionFilter state by the positionFilter reducer.

If you look into the EmployeeList component, you'll notice that the mapStateToProps function has access to both employees and positionFilter pieces of the global state object:

const mapStateToProps = (state) => {
  return {
    employees: getEmployeeList(state.employees, state.positionFilter)
  }
}

NOTE: for the sake of clarity, we are only using components. The Redux approach makes a difference between containers and components. More about it in the future parts of the series.

Employee list filtering

To see how the employee list gets updated check the blue elements flow in the diagram. Let's recap the dispatch flow:

  1. user clicks on a position filter item -> setPositionFilter action is called passing name as payload,

  2. positionFilter reducer function is called, setting the action name as the current state.positionFilter,

  3. updated state.positionFilter is then used in the EmployeeList component to filter the employee array (getEmployeeList function)

That's it for this part. Stay tuned for more details about Redux and React in the upcoming posts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment