Let's continue our work with the simplified redux version of our notes application.

In order to ease our development, let's change our reducer so that the store gets initialized with a state that contains a couple of notes:

const initialState = [
  {
    content: 'reducer defines how redux store works',
    important: true,
    id: 1,
  },
  {
    content: 'state of store can contain any data',
    important: false,
    id: 2,
  },
]

const noteReducer = (state = initialState, action) => {
  // ...
}

// ...
export default noteReducer

Store with complex state

Let's implement filtering for the notes that are displayed to the user. The user interface for the filters will be implemented with radio buttons:

fullstack content

Let's start with a very simple and straightforward implementation:

import React from 'react'
import NewNote from './components/NewNote'
import Notes from './components/Notes'

const App = (props) => {
  const store = props.store

  const filterSelected = (value) => () => {    console.log(value)  }
  return (
    <div>
      <NewNote store={store}/>
      <div>
        <div>
          all          <input type="radio" name="filter"            onChange={filterSelected('ALL')} />          important    <input type="radio" name="filter"            onChange={filterSelected('IMPORTANT')} />          nonimportant <input type="radio" name="filter"            onChange={filterSelected('NONIMPORTANT')} />        </div>
      </div>
      
      <Notes store={store} />
    </div>
  )
}

Since the name attribute of all the radio buttons is the same, they form a button group where only one option can be selected.

The buttons have a change handler that currently only prints the string associated with the clicked button to the console.

We decide to implement the filter functionality by storing the value of the filter in the redux store in addition to the notes themselves. The state of the store should look like this after making these changes:

{
  notes: [
    { content: 'reducer defines how redux store works', important: true, id: 1},
    { content: 'state of store can contain any data', important: false, id: 2}
  ],
  filter: 'IMPORTANT'
}

Only the array of notes is stored in the state of the current implementation of our application. In the new implementation the state object has two properties, notes that contains the array of notes and filter that contains a string indicating which notes should be displayed to the user.

Combined reducers

We could modify our current reducer to deal with the new shape of the state. However, a better solution in this situation is to define a new separate reducer for the state of the filter:

const filterReducer = (state = 'ALL', action) => {
  switch (action.type) {
    case 'SET_FILTER':
      return action.filter
    default:
      return state
  }
}

The actions for changing the state of the filter look like this:

{
  type: 'SET_FILTER',
  filter: 'IMPORTANT'
}

Let's also create a new action creator function. We will write the code for the action creator in a new src/reducers/filterReducer.js module:

const filterReducer = (state = 'ALL', action) => {
  // ...
}

export const filterChange = filter => {
  return {
    type: 'SET_FILTER',
    filter,
  }
}

export default filterReducer

We can create the actual reducer for our application by combining the two existing reducers with the combineReducers function.

Let's define the combined reducer in the index.js file:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'import App from './App'
import noteReducer from './reducers/noteReducer'
import filterReducer from './reducers/filterReducer'
const reducer = combineReducers({  notes: noteReducer,  filter: filterReducer})
const store = createStore(reducer)
console.log(store.getState())

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

Since our application breaks completely at this point, we render an empty div element instead of the App component.

The state of the store gets printed to the console:

fullstack content

As we can see from the output, the store has the exact shape we wanted it to!

Let's take a closer look at how the combined reducer is created:

const reducer = combineReducers({
  notes: noteReducer,
  filter: filterReducer,
})

The state of the store defined by the reducer above is an object with two properties: notes and filter. The value of the notes property is defined by the noteReducer, which does not have to deal with the other properties of the state. Likewise, the filter property is managed by the filterReducer.

Before we make more changes to the code, let's take a look at how different actions change the state of the store defined by the combined reducer. Let's add the following to the index.js file:

import { createNote } from './reducers/noteReducer'
import { filterChange } from './reducers/filterReducer'
//...
store.subscribe(() => console.log(store.getState()))
store.dispatch(filterChange('IMPORTANT'))
store.dispatch(createNote('combineReducers forms one reducer from many simple reducers'))

By simulating the creation of a note and changing the state of the filter in this fashion, the state of the store gets logged to the console after every change that is made to the store:

fullstack content

At this point it is good to become aware of a tiny but important detail. If we add a console log statement to the beginning of both reducers:

const filterReducer = (state = 'ALL', action) => {
  console.log('ACTION: ', action)
  // ...
}

Based on the console output one might get the impression that every action gets duplicated:

fullstack content

Is there a bug in our code? No. The combined reducer works in such a way that every action gets handled in every part of the combined reducer. Typically only one reducer is interested in any given action, but there are situations where multiple reducers change their respective parts of the state based on the same action.

Finishing the filters

Let's finish the application so that it uses the combined reducer. We start by changing the rendering of the application and hooking up the store to the application in the index.js file:

ReactDOM.render(
  <App store={store} />,
  document.getElementById('root')
)

Next, let's fix a bug that is caused by the code expecting the application store to be an array of notes:

fullstack content

There is an easy fix for this. We simply have to change the reference to the array of notes from store.getState() to store.getState().notes:

const Notes = ({ store }) => {
  return(
    <ul>
      {store.getState().notes.map(note =>        <Note
          key={note.id}
          note={note}
          handleClick={() => store.dispatch(toggleImportanceOf(note.id))}
        />
      )}
    </ul>
  )
}

Let's extract the visibility filter into its own src/components/VisibilityFilter.js component:

import React from 'react'
import { filterChange } from '../reducers/filterReducer'

const VisibilityFilter = (props) => {

  const filterClicked = (value) => {
    props.store.dispatch(filterChange(value))
  }

  return (
    <div>
      all    
      <input 
        type="radio" 
        name="filter" 
        onChange={() => filterClicked('ALL')}
      />
      important   
      <input
        type="radio"
        name="filter"
        onChange={() => filterClicked('IMPORTANT')}
      />
      nonimportant 
      <input
        type="radio"
        name="filter"
        onChange={() => filterClicked('NONIMPORTANT')}
      />
    </div>
  )
}

export default VisibilityFilter

With the new component App can be simplified as follows:

import React from 'react'
import Notes from './components/Notes'
import NewNote from './components/NewNote'
import VisibilityFilter from './components/VisibilityFilter'

const App = (props) => {
  const store = props.store

  return (
    <div>
      <NewNote store={store} />
      <VisibilityFilter store={store} />
      <Notes store={store} />
    </div>
  )
}

export default App

The implementation is rather straightforward. Clicking the different radio buttons changes the state of the store's filter property.

Let's change the Notes component to incorporate the filter:

const Notes = ({ store }) => {
  const { notes, filter } = store.getState()  const notesToShow = () => {    if ( filter === 'ALL' ) {      return notes    }    return filter === 'IMPORTANT'      ? notes.filter(note => note.important)      : notes.filter(note => !note.important)  }
  return(
    <ul>
      {notesToShow().map(note =>        <Note
          key={note.id}
          note={note}
          handleClick={() => store.dispatch(toggleImportanceOf(note.id))}
        />
      )}
    </ul>
  )
}

Notice how the properties of the store are assigned to helper variables with the destructuring syntax:

const { notes, filter } = store.getState()

The syntax above is the same as if we were to write:

const notes = store.getState().notes
const filter = store.getState().filter

You can find the code for our current application in its entirety in the part6-2 branch of this Github repository.

There is a slight cosmetic flaw in our application. Even though the filter is set to ALL by default, the associated radio button is not selected. Naturally this issue can be fixed, but since this is an unpleasant but ultimately harmless bug we will save the fix for later.

Connect

The structure of our current application is quite modular thanks to Redux. Naturally, there's still room for improvement.

One unpleasant aspect of our current implementation is that the Redux store has to be passed via props to all of the components that use it. The App component does not actually need Redux for any other purpose than passing it to its children:

const App = (props) => {
  const store = props.store

  return (
    <div>
      <NewNote store={store}/>  
      <VisibilityFilter store={store} />    
      <Notes store={store} />
    </div>
  )
}

To get rid of this unpleasantness we will use the connect function provided by the React Redux library. This is currently the de facto solution for passing the Redux store to React components.

It takes some time to wrap your head around how connect works, but your efforts will be rewarded. Next, let's take a look at how connect is used in practice.

npm install --save react-redux

In order to use the connect function we have to define our application as the child of the Provider component that is provided by the React Redux library. Additionally, the Provider component must receive the Redux store of the application as its store attribute.

Let's make these changes to the index.js file of our application:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'import App from './App'
import noteReducer from './reducers/noteReducer'
import filterReducer from './reducers/filterReducer'

const reducer = combineReducers({
  notes: noteReducer,
  filter: filterReducer
})

const store = createStore(reducer)

ReactDOM.render(
  <Provider store={store}>    <App />  </Provider>,  document.getElementById('root')
)

Let's start by taking a closer look at the Notes component. The connect function can be used for transforming "regular" React components so that the state of the Redux store can be "mapped" into the component's props.

Let's first use the connect function to transform our Notes component into a connected component:

import React from 'react'
import { connect } from 'react-redux'import Note from './Note'
import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = ({ store }) => {
  // ...
}

const ConnectedNotes = connect()(Notes)export default ConnectedNotes

The module exports the connected component that works exactly like the previous regular component for now.

The component needs the list of notes and the value of the filter from the Redux store. The connect function accepts a so-called mapStateToProps function as its first parameter. The function can be used for defining the props of the connected component that are based on the state of the Redux store.

If we define:

const Notes = (props) => {
  // ...
}

const mapStateToProps = (state) => {
  return {
    notes: state.notes,
    filter: state.filter,
  }
}

const ConnectedNotes = connect(mapStateToProps)(Notes)

export default ConnectedNotes

The Notes component can access the state of the store directly, e.g. through props.notes that contains the list of notes. Contrast this to the previous props.store.getState().notes implementation that accessed the notes directly from the store. Similarly, props.filter references the value of the filter.

The component changes in the following way:

const Notes = (props) => {  const notesToShow = () => {
    if ( props.filter === 'ALL' ) {      return props.notes    }

    return props.filter === 'IMPORTANT'      ? props.notes.filter(note => note.important)      : props.notes.filter(note => !note.important)  }

  return(
    <ul>
      {notesToShow().map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() =>
            props.store.dispatch(toggleImportanceOf(note.id))
          }
        />
      )}
    </ul>
  )
}

The situation that results from using connect with the mapStateToProps function we defined can be visualized like this:

fullstack content

The Notes component has "direct access" via props.notes and props.filter for inspecting the state of the Redux store.

The Notes component still uses the dispatch function that it receives through its props to modify the state of the Redux store:

<Note
  key={note.id}
  note={note}
  handleClick={() =>
    props.store.dispatch(toggleImportanceOf(note.id))  }
/>

The store prop no longer exists, so altering the state through the function is currently broken.

The second parameter of the connect function can be used for defining mapDispatchToProps which is a group of action creator functions passed to the connected component as props. Let's make the following changes to our existing connect operation:

const mapStateToProps = (state) => {
  return {
    notes: state.notes,
    filter: state.filter,
  }
}

const mapDispatchToProps = {  toggleImportanceOf,}
const ConnectedNotes = connect(
  mapStateToProps,
  mapDispatchToProps)(Notes)

Now the component can directly dispatch the action defined by the toggleImportanceOf action creator by calling the function through its props:

<Note
  key={note.id}
  note={note}
  handleClick={() => props.toggleImportanceOf(note.id)}/>

This means that instead of dispatching the action like this:

props.store.dispatch(toggleImportanceOf(note.id))

When using connect we can simply do this:

props.toggleImportanceOf(note.id)

There is no need to call the dispatch function separately since connect has already modified the toggleImportanceOf action creator into a form that contains the dispatch.

It can take some to time to wrap your head around how mapDispatchToProps works, especially once we take a look at an alternative way of using it.

The resulting situation from using connect can be visualized like this:

fullstack content

In addition to accessing the store's state via props.notes and props.filter, the component also references a function that can be used for dispatching TOGGLE_IMPORTANCE-type actions via its toggleImportanceOf prop.

The code for the newly refactored Notes component looks like this:

import React from 'react'
import { connect } from 'react-redux'
import Note from './Note'
import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = (props) => {
  const notesToShow = () => {
    if ( props.filter === 'ALL' ) {
      return props.notes
    }

    return props.filter === 'IMPORTANT'
      ? props.notes.filter(note => note.important)
      : props.notes.filter(note => !note.important)
  }

  return(
    <ul>
      {notesToShow().map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => props.toggleImportanceOf(note.id)}
        />
      )}
    </ul>
  )
}

const mapStateToProps = (state) => {
  return {
    notes: state.notes,
    filter: state.filter,
  }
}

const mapDispatchToProps = {
  toggleImportanceOf,
}

// we can export directly the component returned by connect
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Notes)

Let's also use connect to create new notes:

import React from 'react'
import { connect } from 'react-redux'
import { createNote } from '../reducers/noteReducer'

const NewNote = (props) => {
  const addNote = (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)
  }

  return (
    <form onSubmit={addNote}>
      <input name="note" />
      <button type="submit">add</button>
    </form>
  )
}

export default connect(
  null,
  { createNote }
)(NewNote)

Since the component does not need to access the store's state, we can simply pass null as the first parameter to connect.

You can find the code for our current application in its entirety in the part6-3 branch of this Github repository.

Referencing action creators passed as props

Let's direct our attention to one interesting detail in the NewNote component:

import React from 'react'
import { connect } from 'react-redux'
import { createNote } from '../reducers/noteReducer'
const NewNote = (props) => {

  const addNote = (event) => {
    event.preventDefault()
    props.createNote(event.target.note.value)    event.target.note.value = ''
  }
  // ...
}

export default connect(
  null,
  { createNote }
)(NewNote)

Developers who are new to connect may find it puzzling that there are two versions of the createNote action creator in the component.

The function must be referenced as props.createNote through the component's props, as this is the version that contains the automatic dispatch added by connect.

Due to the way that the action creator is imported:

import { createNote } from './../reducers/noteReducer'

The action creator can also be referenced directly by calling createNote. You should not do this, since this is the unmodified version of the action creator that does not contain the added automatic dispatch.

If we print the functions to the console from the code (we have not yet looked at this useful debugging trick):

const NewNote = (props) => {
  console.log(createNote)
  console.log(props.createNote)

  const addNote = (event) => {
    event.preventDefault()
    props.createNote(event.target.note.value)
    event.target.note.value = ''
  }

  // ...
}

We can see the difference between the two functions:

fullstack content

The first function is a regular action creator whereas the second function contains the additional dispatch to the store that was added by connect.

Connect is an incredibly useful tool although it may seem difficult at first due to its level of abstraction.

Alternative way of using mapDispatchToProps

We defined the function for dispatching actions from the connected NewNote component in the following way:

const NewNote = () => {
  // ...
}

export default connect(
  null,
  { createNote }
)(NewNote)

The connect expression above enables the component to dispatch actions for creating new notes with the props.createNote('a new note') command.

The functions passed in mapDispatchToProps must be action creators, that is, functions that return Redux actions.

It is worth noting that the mapDispatchToProps parameter is a JavaScript object, as the definition:

{
  createNote
}

Is just shorthand for defining the object literal:

{
  createNote: createNote
}

Which is an object that has a single createNote property with the createNote function as its value.

Alternatively, we could pass the following function definition as the second parameter to connect:

const NewNote = (props) => {
  // ...
}

const mapDispatchToProps = dispatch => {  return {    createNote: value => {      dispatch(createNote(value))    },  }}
export default connect(
  null,
  mapDispatchToProps
)(NewNote)

In this alternative definition, mapDispatchToProps is a function that connect will invoke by passing it the dispatch-function as its parameter. The return value of the function is an object that defines a group of functions that get passed to the connected component as props. Our example defines the function passed as the createNote prop:

value => {
  dispatch(createNote(value))
}

Which simply dispatches the action created with the createNote action creator.

The component then references the function through its props by calling props.createNote:

const NewNote = (props) => {
  const addNote = (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)
  }

  return (
    <form onSubmit={addNote}>
      <input name="note" />
      <button type="submit">add</button>
    </form>
  )
}

The concept is quite complex and describing it through text is challenging. In most cases it is sufficient to use the simpler form of mapDispatchToProps. However, there are situations where the more complicated definition is necessary, like if the dispatched actions need to reference the props of the component.

The creator of Redux Dan Abramov has created a wonderful tutorial called Getting started with Redux that you can find on Egghead.io. I highly recommend the tutorial to everyone. The last four videos discuss the connect method, particularly the more "complicated" way of using it.

Presentational/Container revisited

The Notes component uses the notesToShow helper method to construct the list of notes that are shown based on the selected filter:

const Notes = (props) => {
  const notesToShow = () => {
    if ( props.filter === 'ALL' ) {
      return props.notes
    }

    return props.filter === 'IMPORTANT'
      ? props.notes.filter(note => note.important)
      : props.notes.filter(note => !note.important)
  }

  // ...
}

It is unnecessary for the component to contain all of this logic. Let's extract it outside of the component so that it is handled in mapStateToProps:

import React from 'react'
import { connect } from 'react-redux'
import Note from './Note'
import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = (props) => {
  return(
    <ul>
      {props.visibleNotes.map(note =>        <Note
          key={note.id}
          note={note}
          handleClick={() => props.toggleImportanceOf(note.id)}
        />
      )}
    </ul>
  )
}

const notesToShow = ({ notes, filter }) => {  if (filter === 'ALL') {
    return notes
  }
  return filter === 'IMPORTANT'
    ? notes.filter(note => note.important)
    : notes.filter(note => !note.important)
}

const mapStateToProps = (state) => {
  return {
    visibleNotes: notesToShow(state),  }
}

const mapDispatchToProps = {
  toggleImportanceOf,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Notes)

Previously mapStateToProps was simply used for selecting pieces of state from the store, but in this case we are also using the notesToShow function to map the state into the desired filtered list of notes. The new version of notesToShow receives the store's state in its entirety, and selects an appropriate piece of the store that is passed to the component. Functions like this are called selectors.

Our new Notes component is almost entirely focused on rendering notes and is quite close to being a so-called presentational component. According to the description provided by Dan Abramov, presentation components:

  • Are concerned with how things look.
  • May contain both presentational and container components inside, and usually have some DOM markup and styles of their own.
  • Often allow containment via props.children.
  • Have no dependencies on the rest of the app, such as Redux actions or stores.
  • Don’t specify how the data is loaded or mutated.
  • Receive data and callbacks exclusively via props.
  • Rarely have their own state (when they do, it’s UI state rather than data).
  • Are written as functional components unless they need state, lifecycle hooks, or performance optimizations.

The connected component that is created with the connect function:

const notesToShow = ({notes, filter}) => {
  // ...
}

const mapStateToProps = (state) => {
  return {
    visibleNotes: notesToShow(state),
  }
}

const mapDispatchToProps = {
  toggleImportanceOf,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Notes)

Fits the description of a container component. According to the description provided by Dan Abramov, container components:

  • Are concerned with how things work.
  • May contain both presentational and container components inside but usually don’t have any DOM markup of their own except for some wrapping divs, and never have any styles.
  • Provide the data and behavior to presentational or other container components.
  • Call Redux actions and provide these as callbacks to the presentational components.
  • Are often stateful, as they tend to serve as data sources.
  • Are usually generated using higher order components such as connect from React Redux, rather than written by hand.

Dividing the application into presentational and container components is one way of structuring React applications that has been deemed beneficial. The division may be a good design choice or it may not, it depends on the context.

Abramov attributes the following benefits to the division:

  • Better separation of concerns. You understand your app and your UI better by writing components this way.
  • Better reusability. You can use the same presentational component with completely different state sources, and turn those into separate container components that can be further reused.
  • Presentational components are essentially your app’s “palette”. You can put them on a single page and let the designer tweak all their variations without touching the app’s logic. You can run screenshot regression tests on that page.

Abramov mentions the term high order component. The Notes component is an example of a regular component, whereas the connect method provided by React-Redux is an example of a high order component. Essentially, a high order component is a function that accept a "regular" component as its parameter, that then returns a new "regular" component as its return value.

High order components, or HOCs, are a way of defining generic functionality that can be applied to components. This is a concept from functional programming that very slightly resembles inheritance in object oriented programming.

HOCs are in fact a generalization of the High Order Function (HOF) concept. HOFs are functions that either accept functions as parameters or return functions. We have actually been using HOFs throughout the course, e.g. all of the methods used for dealing with arrays like map, filter and find are HOFs.

You can find the code for our current application in its entirety in the part6-4 branch of this Github repository.

Note the changes in the VisibilityFilter component and removal of all the props in the App component.