State of React #2 – From Inception to Redux

Welcome back to the second instalment of State of React! In case you missed it, the first instalment demonstrated a small app without component state. And it received a bunch of interesting responses, like this one from @metapgmr:

using props as state and claiming that components are … stateless! The React Emperor has no clothes

Yes, the app was basically wearing its birthday suit. And yes, the app did contain state. But no, the state wasn’t in the props – and it wasn’t component state either.

So what was it?

In case you missed it, here is the “React Inception” pattern (thanks for the great name, @mctaylorpants):

class CommentForm extends React.Component {
    static propTypes = {
        value: React.PropTypes.object.isRequired,
        onChange: React.PropTypes.func.isRequired,
    }

    handleChange(field, e) {
        this.props.onChange({
            ...this.props.value,
            [field]: e.target.value,
        })
    }

    render() {
        return (
            <div>
                <input
                    placeholder="Name"
                    value={this.props.value.name}
                    onChange={this.handleChange.bind(this, 'name')}
                />
                <input
                    placeholder="Message"
                    value={this.props.value.message}
                    onChange={this.handleChange.bind(this, 'message')}
                />
            </div>
        )
    }
}

// Handle updated values by re-rendering the app
function render(value) {
    ReactDOM.render(
        <CommentForm
            value={value}
            onChange={render}
        />,
        document.getElementById("react-app")
    )
}

// Initialise the app by rendering it
render({
    name: "James",
    message: "",
})

Think you know where the state is stored? Check your answer by touching or hovering your mouse over this box:

The state is stored in the DOM. By calling ReactDOM.render with the new value, we write it to the DOM. The DOM then merges it with the requested changes on every keystroke, and passes it back to the app via an onChange handler.

Okay. So the app isn’t stateless. That means the pattern is basically useless, right?

Not so fast. As I mentioned last week, having all our state in one place is pretty handy. But that brings us to the bonus question:

So why don’t people do this?

I’d try and explain this, but Joe Shelby beat me to the punch in a comment on last week’s article:

When you get to large apps, having EVERY single component look through the part of the ‘value’ object to see if anything has changed would be impressively slow.

To get an idea of why this is, you need to remember that every JSX statement in a React file is actually a function call, which creates a ReactElement object.

By rendering your page from the root, you need to run the render method of every component on your page. Which will then create a new ReactElement object for every JSX element on your page. Which will then then be diffed with all the React Elements on the previous version of your page. And all you wanted to do was add an active class to your spinner div.

This is a big reason why people use setState in the real world. setState lets you update a subtree of your react app, without re-rendering the whole thing. But it also lets you use react-router.

What has react-router got to do with setState?

If you’ve ever used react-router, one of the things you may have noticed is that it doesn’t let you pass props to your components. Instead, you need to pass components. Here is an example from the docs:

ReactDOM.render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="about" component={About}/>
      <Route path="*" component={NoMatch}/>
    </Route>
  </Router>
), document.getElementById('root'))

In this example, App, About and NoMatch are all React Components – i.e. the objects returned by React.createClass, or classes which extend React.Component. By passing components to routes, you’re able to easily configure routes at the very root of your application. But by only being able to pass components to routes, you also lose the ability to pass props from the parent component without using context.

I’m not going to get into the merits/demerits of designing a router this way. But I will say that one consequence of doing things this way is that it removes the possibility of building your app using props passed all the way from the root. This means that the React Inception pattern won’t work. And honestly, this probably factors more into people’s decision to avoid the pattern than performance does.

So to summarise – the React Inception pattern is a fun novelty which doesn’t really have any place in the real world. Except that you may already be using it without realising it — just modified in a way to negate these issues.

Making React Inception Useful

To fix the two issues mentioned above, we’ll need to make two changes:

  1. We’ll need to split our render function into two parts — one to make changes to state, and one to handle changes in that state.
  2. We’ll need to make it possible to update the state instead of replacing it, as updates from one part of the application shouldn’t affect state used by other parts of the application.

Simple, right? And the implementation is even simpler:

class Store {
  constructor() {
    this.listeners = []
    this.state = {}
  }

  subscribe(listener) {
    this.listeners.push(listener)

    // Return a function allowing us to unsubscribe
    return () => this.listeners.splice(this.listeners.indexOf(listener), 1)
  }

  updateState = (updates) => {
    this.state = Object.assign({}, this.state, updates)
    for (let listener of this.listeners) {
      listener(this.state)
    }
  }
}

const store = new Store()

To use the store, pass its updateState method through to components which need to store state. And then use subscribe to pass the latest version of the state into the components.

As an example, this is how we’d implement our familiar React Inception pattern using a Store:

store.subscribe(state => {
  ReactDOM.render(
    <CommentForm
        value={state}
        onChange={store.updateState}
    />,
    document.getElementById("react-app")
  )
})

// Set initial state
store.updateState({
    name: "James",
    message: "",
})

But this just makes our existing code more complicated. What we really want to do is connect our store up to a component inside the application, speeding up our app and getting our data past react-router without using props.

To do so, let’s follow Dan Abramov’s Containers pattern. This is akin to creating a “sub-application” which uses setState to store the data for a group of state-free child components — like the CommentForm from our previous app.

class CommentFormContainer extends React.Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(state => {
      this.setState(state.CommentFormContainer)
    })
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe()
    }
  }

  handleChange(value) {
    store.updateState({ CommentFormContainer: value })
  }

  render() {
    return (
      <CommentForm
        value={this.state}
        onChange={this.handleChange}
      />
    )
  }
}

Now place this Container somewhere inside an existing application and presto — (almost) stateless sub-app.

Wait a moment – haven’t I seen this before?

Container components? Aren’t they a Redux thing? And stores with a subscribe method which hold the application state at the root of the app?

So I admit it. I’ve basically made a shit version of Redux.

Honestly, I wouldn’t use the code I’ve written above — Redux is better. Reasons:

  • Instead of allowing direct mutation of the stored state, it forces you to write actions and reducers which make it easier to understand what-the-hell is going on.
  • Redux gives you the connect function to automate creation of container components (my container above is based on the internals of connect).
  • It gives you extensibility via middleware, and a whole bunch of neat tooling.

So what I’m saying is that if you want to manage state, Redux is the bees knees. By putting it all in one place, it becomes possible to share state between Containers and cache data between page views. And by keeping most of your components stateless, testing and design becomes a breeze.

But while Redux does a great job of managing your state, it still leaves you with the problem of structuring it.

5 Types of state

I have a theory: to understand how to structure state, you first need to understand where the state actually is.

As far as I can tell, there are five types of state — and the best way to structure state depends on which of the types it falls under.

Want to find out what the types are, and the patterns for structuring them? Then subscribe to my newsletter now to make sure you don’t miss the next instalment of State of React! And in return for your e-mail, you’ll immediately receive five free ReactJS and JavaScript cheatsheets!

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

One more thing – I love hearing your questions, offers and opinions. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!

1 Comment State of React #2 – From Inception to Redux

Leave a Reply

Your email address will not be published.