Redux, Immutability, and Higher Order Components

As I wrote in a previous blog post, immutability is an important concept that can bring many benefits to development. In fact, it's been embraced by javascript land, most notably by the ultra-popular framework, Redux.

I find that when people first switch to using Redux and React from other frameworks, they're a bit confused about how immutability comes into play.

And usually what helps them understand the importance of immutability lies in a higher order component in react-redux.

But what exactly is a Higher Order Component?

A higher order component is exactly what it sounds like -- it is the React component equivalent of a higher order function.

In functional programming, a higher order function is a function that accepts another function as an argument and returns another function, like this one:

applyTwice :: (a -> a) -> a -> a  
applyTwice f x = f (f x)  

ghci> applyTwice ("SAME " ++) "BUT DIFFERENT"  
"SAME SAME BUT DIFFERENT"

A simple example of a higher order function in Haskell that takes a function and applies it twice to something.

In React, a higher order component is a function that accepts a component and returns an enhanced component with superhero abilities.

Here's a simple example: imagine needing to add the functionality of tracking how many clicks occurred on various components in your user interface.

A way to do this is through wrapping a component with a higher order component to enhance it with this desired functionality, like so:

let ClickTracker = InnerComponent => class extends React.Component {  
  constructor(){
    super();
    this.update = this.update.bind(this);
    this.state = {numClicks: 0}
  }
  update(){
    this.setState({numClicks: this.state.numClicks + 1})
  }
  componentWillMount(){
    console.log('will mount')
  }
  render(){
    return <InnerComponent
      update={this.update}
      {...this.state}
      {...this.props} />
  }
  componentDidMount(){
    console.log('mounted')
  }
}

const Button = (props) => <button  
                            onClick={props.update}>
                            {props.txt}: {props.numClicks}
                          </button>

const Div = (props) => <div  
                           onClick={props.update}>
                           {props.txt}: {props.numClicks}
                       </div>

let ClickTrackedButton = ClickTracker(Button)  
let ClickTrackedDiv = ClickTracker(Div)

class App extends React.Component {

  render(){
    return (
      <div>
        <ClickTrackedButton txt="Button" />
        <ClickTrackedDiv txt="Div" />
      </div>
    );
  }

}

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

As you can see, our ClickTracker function enhances its input component with new functionalities, while avoiding all of the disadvantages of working with mixins.

So what do higher order components have to do with Redux?

Well, arguably, one of the key concepts when using Redux with React lies in a higher order component: connect().

Consider the following react/redux snippet with connect() from a TodoMVC example:

const TodoList = ({  
  todos,
  onTodoClick
}) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
);

const mapStateToProps = (state) => {  
  return {
    todos: getVisibleTodos(
      state.todos,
      state.visibilityFilter
    )
  };
};

const mapDispatchToProps = (dispatch) => {  
  return {
    onTodoClick: (id) => {
      dispatch({
        type: 'TOGGLE_TODO',
        id
      });
    }
  };
};

const { connect } = ReactRedux;  
const VisibleTodoList = connect(  
  mapStateToProps,
  mapDispatchToProps
)(TodoList);

Notice what connect() is doing. connect() is connecting the TodoList React component to a Redux store. connect() is, through mapStateToProps(), acquiring the necessary state needed to render for VisibleTodoList by calculating the visible todos to render, and passing them as props to TodoList. In addition, connect() is, through mapDispatchToProps(), subscribing the TodoList component to a change handler that will toggle a todo when the todo is clicked.

The key here is that when the change handler is called, connect() compares the current state of the store with the new state to see if there are any changes (prevStoreState === storeState), and if there is, it will update the input component using setState().

This is where immutability comes into play. Comparing prevStoreState with storeState is a lot faster with identity comparison rather than a shallow or deep comparison. This can only be achieved if the store is immutable. Otherwise, if the store is not immutable, and the state is mutated, the input component will not re-render.

if (pure && prevStoreState === storeState) {  
  return
}

The lines of code in react-redux that prevent a change from happening if the store has not changed.

To optimize performance, this comparison function needs to be used with immutable data structures, otherwise checking for equality is just way too slow. Checking if any subset of data has been altered very efficient is super easy with immutability because all it takes is a simple identity check.

I hope that helps clear up some things about Redux and React. It's exciting times in the industry to see a lot of these interesting concepts play such an important role in the landscape - and I plan to cover more of them in future posts.

comments powered by Disqus