/ Engineering

Managing State in the React Eco-system (Part 1)

One of the driving technologies that Rockstar Cafe leverages is React and Redux (check out the rest of our tech stack in this blog post). Coming from Angular 1.x, React was a huge departure from Angular 1.x. We'll share some of the lessons we've learned about managing state in React.

The first part of this blog will go over managing state in components, the second part of the blog will introduce type of states that would make sense to manage in redux

1. Purely presentational (dumb) components

One of the fundamental philosophies behind React is making the view a function of the props that are passed in, this led to two main types of react components: Smart and Dumb components. By leveraging uni-directional data flow we're able to increase the reuse of purely presentational components. Dumb components don't care about where the data is coming from, only that it will receive it from some source.

let RowsFound = ({count}) => <span>{count}</span>
let Table = ({ rows, headers, showDataCount }) => (
    <div>
        {showDataCount && <RowsFound count={rows.length} /> }
        {headers.map(header => (
            <h1>{header}</h1>
        }
        {rows.map(...)}

2. Smart Components (a.k.a. Containers):

Now that we have dumb components that makes the view a function of the props passed in, we're really just left with understanding how to manage the state in our application. Smart components are basically designed to just inject props into dumb components, they can manage the state using setState. This typically works very well for states such as which tab you're on or whether a modal is on or not.

import Table from '...'

let tableContainer = (InnerComponent) => {
    class TableContainer extends React.Component {
    
        constructor() {
            this.showTotal = false
        }
        
        setShowTotal(showTotal) {
            this.setState({ showTotal })
        }

        render() {
            return (<div>
                <ShowTotalToggler isShowingTotal={this.showTotal} onChange={this.setShowTotal} />
                <InnerComponent rows={this.rows} headers={this.headers} showTotal={this.showTotal}/>
            </div>
            
        }
    }
    
    return TableContainer
}

export default tableContainer(Table)

3. withXXXX (Higher Order Components / HoCs)

Now Smart Components are actually higher order components, which is just a fancy term for components that take in other components. But there are times in which you want to reuse the logic you write in your container. An example could be whether a specific modal is opened or closed.

withOpenStateController = ({ defaultState = false }) => (WrappedComponent) => {
    class OpenStateController extends React.Component {
    
        constructor() {
            this.isOpen = defaultState || false
        }
        
        toggleState() {
            this.setState({ isOpen: this.isOpen })
        }
        
        render() {
            return <WrappedComponent 
                        toggleState={this.toggleState} 
                        isOpen={this.isOpen} />
            
        }
    }
    
    return OpenStateController
}

withOpenStateController({ defaultState: true })(SomeModal)

As you can see, HoCs are extremely powerful and lets you compose behaviour together and lets you reuse logic.

Hint: Check out Recompose for creating HoCs with little to no boilerplate.

So when do we need to use Redux?

Let's say that we wanted to use withOpenStateController and use its toggleState much lower (imagine 10 nested components lower) whilst we use the isOpen prop 1 component deep. It wouldn't make much sense to pass toggleState all the way down 10+ components, it would introduce way too much coupling.


4. Redux Action Creators & Store

Redux Action Creators enable you to connect your components at anytime to a single store and a set of actions you can perform. This enables you to decouple the act of performing an action and selecting a value from the store.

If we were to refactor withOpenStateController what we'd do is basically creator a set of action creators, and do :

connect(mapStateToProps)(SomeModal) - to inject the is open prop

To inject the toggleState action, you simply need to connect your component with the action creator.

connect(null, mapDispatchToActionCreators(dispatch => ({
    toggleState: dispatch(ActionCreator.toggleState())
}))(AnyButtonOrFormInYourApp)

In the next part we'll talk about the type of data we manage in redux as well as when it makes sense to store something in redux vs storing it in HoCs / Smart Components.