blog.jakoblind.no

Code your own Redux part 2: the connect function

Do you feel unsure whether you really, really understand Redux? You might have a general idea of how it works, but yet you don’t know it to a 100%. You have already made applications, read documentation, articles, tutorial and blog posts. What more can you do to truly know everything about this framework?

One way to ensure you have a deep understanding of Redux is to dig into the source code. When you know how the source code works you will be able to confidently create complex applications with React and Redux.

This is part two in my “code your own Redux”-series. In part one we coded a fully working Redux implementation in 18 lines of code! Go check it out if you haven’t already. Now it’s time to code the function that connects Redux to React! This time we will have to do some simplifications so the end result will not be a runnable app, but you will get an understanding how the implementation of connect works. It’s a great learning experience!

A quick look the usage of react-redux

There is an officially supported library for connecting Redux with React called react-redux that we are going to use to base our implementation on. I will give a super quick step-by-step tutorial (only 2 steps!) of the usage of this library so we know how it works before we will implement it ourselves!

1. Make your store accessible from React

First, we will wrap the root component of our app in a react-redux component which takes our store as props. This wrapping component will then make the store available for all React components.

import { Provider } from "react-redux"
const store = createStore(myReducer)
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
)

2. Make your component access Redux data

Let’s say you have a simple component that you want to be able to receive data from redux.

let AddTodo = ({ todos }) => {
  // Some fancy implementation (not relevant for this example)
}

react-redux has a function called connect()  that you can use to connect your Redux store to your React components.

const mapStateToProps = (state) => {
  return {
    todos: state.todos,
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    },
  }
}
AddTodo = connect(mapStateToProps, mapDispatchToProps)(AddTodo)

The connect function automatically takes the data from the store, and passes it down as props to the connected component. When the data in the store changes, the passed down props changes, and the component is automatically re-rendered. Cool!

If you need a more detailed introduction to this library I highly suggest reading the docs about it here before moving on.

Code our own connect()

Now that we know how to use react-redux, let’s implement it!

A look at the input parameters

The first thing you notice is that connect has two sets of parameters. If you are new to JavaScript this might look a bit weird. In Javascript functions are ”first class citizens”. That means you can pass functions as parameters to other functions, assign them to variables, and even return functions from other functions. And that is what has happened here.

The connect function returns a new function that is then immediately called. We now have enough knowledge to implement the skeleton for the connect function:

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    //something happens here
  }
}

If you are new to JavaScript, this might be a bit tricky to wrap your head around. I suggest spending a few minutes playing around with that code until you are confident in what it does.

Component in - Component out

If we take a look at how the connect function is used, we can see that it is a function that takes our React component as input, and outputs a new React component. Let’s add that to our implementation.

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    //we return a Wrapper component:
    return class extends React.Component {
      render() {
        return <WrappedComponent {...this.props} />
      }
    }
  }
}

So now our function returns the exact component we sent as input (here named WrappedComponent ) with an empty wrapper component around it.

This code is not very useful because we don’t do anything with the component. Let’s fix that! In the next step we will enhance the component with data from our store.

Adding props to the returned component

We want to pass down props containing data from the store to our newly created component. We will do it like this:

  1. Get the state from the store with store.getState()

  2. Call the function mapStateToProps that has been passed in

  3. Set the returned data as props in the newly created component

It looks like this:

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(store.getState(), this.props)}
          />
        )
      }
    }
  }
}

So we have a function which takes a React component as input and returns a new enhanced React component. This actually has a fancy name: Higher order component (HOC). It’s a pattern that is commonly used in advanced React applications and React libraries because it allows you to elegantly reuse logic. And that is precisely what we have done here.

Add Action methods to the returned component

We also want our React component to be able to call actions with the dispatcher. We pass down the actions from the mapDispatchToProps function as props to our components in a similar way that we did in the previous step.

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(store.getState(), this.props)}
            {...mapDispatchToProps(store.dispatch, this.props)}
          />
        )
      }
    }
  }
}

But wait, where did we get the store from?

Remember in the beginning of this post where I showed how to use react-redux? I said that you need to wrap the application root component in a Provider component which injects the store to all your components. That is how we get the store here! How this is implemented is out of scope for this post, but maybe we can come back to it in a follow-up post. In the meantime I will give you a hint. It is using react context.

Subscribing to the store

Now we have a way to map the data from the store to props to our components. We can also dispatch actions from our component.

We are just missing one thing right now. We need to make sure we don’t miss any updates from our store by subscribing to it.

function connect(mapStateToProps, mapDispatchToProps) {
  return function (WrappedComponent) {
    return class extends React.Component {
      render() {
        return (
          <WrappedComponent
            {...this.props}
            {...mapStateToProps(store.getState(), this.props)}
            {...mapDispatchToProps(store.dispatch, this.props)}
          />
        )
      }
      componentDidMount() {
        this.unsubscribe = store.subscribe(this.handleChange.bind(this))
      }

      componentWillUnmount() {
        this.unsubscribe()
      }

      handleChange() {
        this.forceUpdate()
      }
    }
  }
}

Now we have subscribed for changes in our store with a callback function handleChange. Our callback function calls the React function forceUpdate which, not very surprisingly, forces a re-render of our component!

We are done!

At least for this simplified example. I don’t want to advise you to use this in production because it is not runnable. It’s for educational purposes only!

This blog post was inspired by this gist by Dan Abramov (founder of Redux) that gave me the aha-moment of how simple it was!

Redux is a surprisingly small library, and it’s fully possible to understand a lot by reading the source code.

Next: Code your own combine reducers


tags: