Build your own Redux - Redvike
31 May 2019

Build your own Redux

31 May 2019

Build your own Redux

Author:

Avatar
Jan Michalak
31 May 2019

Build your own Redux

Author:

Avatar
Jan Michalak

If you have ever worked with React, you probably heard about Redux. Redux is a predictable state container for JavaScript Apps. In this piece, I would explain how it works and what is flux architecture. Why Flux Architecture? Because this is the group of paradigms on which Redux is based. The goal of this article is to prepare you to use the full potential of Redux by understanding the way it works and how it’s made. Through this article, I would extend the topic by building my own mini-Redux to show you a practical way of using Flux Architecture.

What Flux Architecture is about?

The main rule of FLUX is one way data flow in our application. Data migrates through the action dispatcher to the store (global state) from which the data goes to the view – React components. Usually, actions are triggered by elements of application like inputs or buttons.

If you want to learn more about FLUX I suggest discovering documentation made by Facebook’s engineers. They invented and shared this approach as well as a brilliant presentation about it.

Let’s built our own React-Redux

Thanks to the theoretical knowledge, we can make our own implementation of Flux architecture which would enhance our React app. We would need to use parts like store, dispatchers, and access to the stored data which we would get thanks to the subscription.

PubSub is another pattern, which redux inventors used while building their own library. So as you may already recognize they haven’t invented anything new

In case of this particular pattern, we won’t dig deep into it’s theoretical background. Instead of it let’s get straight to the way its implemented.

Store

Let’s start from creating Start class. It is responsible for storing the state.

class Store {
  constructor(reducer, initialState = {}) {
    this.state = initialState || {};  
    this.reducer = reducer;
  }
}

const reducer = (state, action) => {  
   return state;  
};  
  
const initialState = {  
  user: {  
    name: "John Doe",  
    money: 0  
  }  
};  
  
const store = new Store(reducer, initialState);

Now we have a store which can be initialized with reducer and initial state. The next part which we need is methods. Methods which would allow as to trigger actions and return the new state. However, we would need a way to access this data and the possibility to change them. The publish-subscribe pattern would help us in this.

class Store {  
  constructor(reducer, initialState) {  
    this.state = initialState || {};  
    this.reducer = reducer;  
    this.listeners = [];  
  }  
  
  subscribe(fn) {  
    this.listeners.push(fn);  
  }
  
    dispatch(action) {  
    this.state = this.reducer(this.state, action);  
    this.listeners.map(fn => fn());
    } 
  
  getState() {  
    return this.state;  
  }
 }

Thanks to these additional lines of code, we have the Store equipped with four basic methods

Subscribe

this.listeners = []; 

subscribe(fn) {
 this.listeners.push(fn);  
}

We create a table with listeners in which we would store all of the functions which would monitor changes in our state. We are able to achieve it thanks to the subscribe method which takes the name of the function as a parameter.

store.subscribe(() => {
  console.log('state has been updated')
  console.log(store.getState());
})

It allows us to react to any change of our state.

subscribe(fn) {  
  this.listeners.push(fn);  
  return {  
    unsubscribe: () => {  
      this.listeners = this.listeners.filter(el => el !== fn);  
    } 
  }
}

Let’s add an option to end subscription through unsubscribe too.

this.subscribtion = store.subscribe(() => {}) // listening store’s state changes
this.subscribtion.unsubscribe() // cancel listening

It helps to optimize the usage of browser memory because when we do not need to monitor state, we can delete our function from a table of listeners by filtering it out. For example, you can execute subscription in React’s componentDidMount and then stop monitoring when the component would unmount and trigger unsubscribe in componentWillUnmount.

Dispatch

We also create a dispatch function. It uses an object with a type of action as a parameter.

this.state = this.reducer(this.state, action);

In dispatch function, we trigger reducer which returns new state based on a triggered action.

this.listeners.map(fn => fn());

In dispatch, we have to provide all our listeners with any changes which occurred. We do it through a simple iteration on the table of listeners by triggering any function, which is saved in this table.

Let’s implement our store in the React App

As you can see in this example, I implemented the store by listening to its changes in componentDidMount by saving our store to the state. Through this way, we inform our app about changes which allows updating our component. I already mentioned this, but it is extremely important – every time you stop using a subscription you should delete it. In this example, I implemented it in componentWillUnmount. This rule is not only accurate and important while working with redux but also in any publish-subscribe pattern based library i. E. rxjs.Unfortunately getting data from global state to our react app isn’t as convenient as it could be. That’s why we would implement a higher order component – connect. It provides us with the possibility to get the state as well as triggering actions from React component.

We would start implementing it from creating a Provider which would be responsible for providing all of the components in our application with the state.

const StoreContext = React.createContext(null);

I use react Context API to do it. In the beginning, I have to create context:

class StoreProvider extends React.Component {  
  render() {  
    const Context = StoreContext;  
    return (  
     <Context.Provider value={{ ...this.props.store }}>  
       {this.props.children}  
     </Context.Provider>  
   ); }
 }

I created the Provider, which takes store as a prop:

const store = new Store(reducer, initialState)

function App () {
  return (
    <StoreProvider store={store}>
      <h1>App</h1>
    </StoreProvider>
  )
}

Lately, while using Context, we would have easy access to our state in react components thanks to this particular part.

However, this Provider lacks listening to changes of the state which occurs after an action is triggered. Let’s resolve this issue now.

class StoreProvider extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { state: props.store.getState() };  
    this.subscribtion = null;  
  }  
  
  componentDidMount() {  
    const {store} = this.props;
    this.subscribtion = store.subscribe(() => {  
      this.setState({ state: store.getState()});
    });
  }  
  
  componentWillUnmount() {  
    this.subscribtion.unsubscribe();  
  }  
  render() {  
    const Context = StoreContext;  
    return (  
     <Context.Provider value={{ ...this.props.store, ...this.state }}>  
       {this.props.children}  
     </Context.Provider>  
   ); }
 }

In the componentDidMount method, we are listening to any changes in the state of our store. In provider, we store state in the local state of the component, so any time we trigger the setState method component would update the value of state and pass it to the <Context.Provider>. Lately, we can use values in <Context.Consumer>.

If you aren’t familiar with Context API, I suggest reading React’s documentation. It contains lots of detailed explanation on this topic: https://reactjs.org/docs/context.html#api

We would use our data in Higher Order Component named connect.

Our connect would contain two functions:

const mapStateToProps = (state, props) => {},  
const mapDispatchToProps = dispatch => ({})

In the first one we are going to map the global state to the component’s props:

const mapStateToProps = (state) => ({ color: state.color })

It helps us to access the value of the global state while we are referring to this.props.color inside of react component.

const mapDispatchToProps = (dispatch) => ({
  changeColor: () => dispatch({ type: "CHANGE_COLOR" })
})

The second function we can impact the change of global state through dispatching action from the component by using props. In this case, we do it by using this.props.changeColor() inside of the component.

We also need to pass component to our HOC. Finally, it would look like this:

const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App)

Let’s implement our connect. We start from the basic HOC.

export const connect = () => WrappedComponent => {
  return class extends React.Component {  
    render() {  
      return <WrappedComponent {...this.props} />
    }
  }
}

When we have a HOC which takes a component and return it without doing anything useful, we can focus on connecting our component to the global store. We download data from the store using Consumer from the React Context API, which we’ve already used in Provider.

import { StoreContext } from "./provider";

export const connect = () => WrappedComponent => {
  return class extends React.Component {
    const Context = StoreContext;
    render() {  
      return (
        <Context.Consumer>
          {({ state, dispatch }) => {
            return <WrappedComponent {...this.props} />
          }
        <Context.Consumer>
      )
    }
  }
}

Using Context.Consumer provides us with access to the global state and dispatch method. We would use both of them while implementing mapStateToProps and mapDispatchToProps inside of our component.

import { StoreContext } from "./provider";

export const connect = (
  mapStateToProps = (state, props) => {},  
  mapDispatchToProps = dispatch => {}
) => WrappedComponent => {
  return class extends React.Component {
    const Context = StoreContext;
    render() {  
      return (
        <Context.Consumer>
          {({ state, dispatch }) => {
            const mappedState = mapStateToProps(state, this.props);  
            const mappedDispatch = mapDispatchToProps(dispatch);
            return (
              <WrappedComponent
                {...this.props}
                {...mappedState}  
                {...mappedDispatch}
              />
            )
          }
        <Context.Consumer>
      )
    }
  }
}

That’s how our whole implementation of connecting looks like and this is our own mini-redux.

Thanks to this, we can pretty easily refactor our whole react app and add state to it by using connect instead of directly referring to the methods inside of the store.

As you can see, the size of our code decreased so much that we can transform it into a functions-based component. Obviously, it is just a simple example and this is not the ideal way of working with React for every other case, but usually, it’s useful. However, I have to advise you to be careful while reducing the size of react component using redux.

Sum up

I hope that this article in which we created our own implementation of React-Redux would help you to understand the basics of working with this library. Moreover, I hope that you would be able to use it in your own project. That’s why I encourage you to look in Redux and React-Redux source code and maybe refining this implementation.

architecture
flux
redux

Related blog posts

Interested? - let us know ☕
[email protected] Get Estimation