State of React #1: A Stateless React App?

Have you ever been frustrated with the task of making loading spinners appear at the right time? Or maybe you’ve had trouble making a popup appear when and where you want it? These are both signs that your code suffers from unmanaged state — the bane of basically every web developer on the planet.

And luckily, the React ecosystem is brimming with tools to help. Redux, MobX, Flux and RxJS are just a few. And while they have a number of things in common (including for some reason the letter “x”), the one which stands out the most is that they just convert the problem of “managing” state into the problem of “structuring it”.

But if it seems that there is no way to dull the state of pain, it begs the question — instead of managing our state, why don’t we just avoid it?

But how?

Well, given a React app generally stores most of its state with setState, it stands to reason that the first thing we’d want to do is ban it.

By definition, this means that if we had a form, we’d need to pass any changes back up to the root of the application via callbacks. And we’d also need to pass any new values down the tree via props.

We’d also rule out the use of uncontrolled components, as they’re inherently stateful.

This would leave something like this:

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>
        )
    }
}

It’s a form? No wait, it’s an input?

Notice how the ContactForm has the same signature as the inputs inside it? By structuring our format to take and emit Contact objects via its value prop and onChange callback, we’ve basically made a “Contact Input”. This form is to Contact objects as an <input> is to strings.

By itself, this is nothing special – but let me throw this at you. If a Contact input can be created from a number of string inputs, can an Invoice input be created from a Contact input, a number of Line Item inputs, and etc.?

The answer, of course, is yes. And the cool part is that this pattern isn’t just limited to forms. For example, we could use a similar pattern of nested callbacks and objects to hold an entire application worth of data.

But whether we’ve banned setState from a single form or an entire application, we’re still going to have props on the root object which hold the app’s callbacks and data:

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

And whatever are we going to do with these?

Takes a value, emits changed values

Well, since our value prop takes exactly the same format as the onChange callback emits, why don’t we just plug them into each other?

// 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: "",
})

Don’t believe its possible? Check for yourself with this JSBin.

Look Ma, No Component State!

By calling render every time anything changes, we ensure our changes are visible, without storing them within any React component’s state.

And just to cap things off, we have access to enough information to render the entire application at any point in the app’s life cycle, all within that single value object. That surely has to be useful.

But wait a minute. If this solves all our problems, why does it seem nobody is doing anything this way? And doesn’t having enough information to render our application stored within value sound suspiciously like state?

So my question to you is: does this app have state? And if so, where is it?

And the bonus question: Why don’t more apps follow this pattern?

Tweet your responses on hash tag #stateofreact, and I’ll respond in next week’s episode of State Of React! Sign up to my newsletter to make sure you don’t miss out — and grab a bunch of exclusive cheat sheets while you’re at it:

I will send you useful articles, cheatsheets and code.

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

Update: Part 2 is now out!

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!

11 Comments State of React #1: A Stateless React App?

  1. MaxArt

    That’s intresting, I ws wondering the same thing too. Why having a second layer of data when everything can be passed forth and back through properties?
    I’m a total React noob so I couldn’t really put these thoughts on the table, nor I actually made any efforts in this sense. I just presumed states were especially helpful when things were getting complicated.

    (By the way, can you change `CommentForm` into `ContactForm`?)

    Reply
    1. Jonathan

      It all depends on your use case, in simple examples the advantages are covered in an excessive amount of boilerplate of course. Once you get to more complex one-page apps the advantages definitely take over. Esp. if you need to maybe even save your application state and recover it for a later use. Don’t think about this concept as having 2 data layers but replacing reacts native layer with a 2nd one.

      Reply
  2. Christian Alfoni

    Hi James and thanks for pointing out the problem so beautifully, “the one which stands out the most is that they just convert the problem of “managing” state into the problem of “structuring it”” 🙂

    What I want to challenge with this approach though is scalability. Have you tried this approach with an application that has hundreds of components and hundreds of state values that needs to move into different parts of the app. And what about render performance? If all props comes from the top it means that all depending components which just passes props down to some nested component somewhere also needs to rerender. Or is this more of a theoretical approach?

    I think also this approach hits an other pain point which the others solve. Passing props all around creates a very rigid component structure, not allowing you to move components around as they completely depend on a potentially complex “chain of passing props”. Also the fact that you pass props down so many components makes it hard to reason about where the props really come from.

    That said it is really great that new patterns are explored, but I think I can theoretically answer your question: “It is not used because it does not scale” 😉

    Reply
  3. Joseph Shelby

    My first thought is scale. 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. All of the (alleged) speed advantages of the shadow-dom would be lost.

    If your app is just a little thing like my work-in-progress playlist editor for Subsonic, that’s probably fine.

    If your app is a wysiwyg editor with a lot of dialogs and buttons for every possible text formatting, that is a lot of components constantly looking at the contained DOM or a state-value container, to determine if they need to in an ‘on’ or ‘off’ state.

    It is the classic trade-off: execution time, memory. The single-render uses a lot less memory (no individual event handlers), but is costly in execution time. At a certain level, that maxes out and using more memory for state management gives much better performance.

    And in mobile, performance is everything.

    Reply
  4. Stephen Cleary

    Very interesting!

    The biggest problem is that there’s no way for external code to affect state, including asynchronous methods. So, a button to download and display data can’t be modeled with the short-circuit approach.

    It’s interesting how close this is to Redux, though: if you pull the state out into a global component and add some functions that can change the state (through a queue, not immediately), then you’re practically at Redux.

    Reply
  5. Michael J Ryan

    It’s still state, it’s just abstracted to a higher level, and it’s stored as component properties… This is really close to what Redux does, minus a persistent store for reducers… although you usually have a larger structure than a form or two…

    You could use a redux-like store passing the state, and dispatch down as properties instead of a higher level component. The point is where you draw the line and still how you manage state.

    In your example, you’d wind up creating many middle-level actions at the component level for child components in a larger application, and this isn’t necessarily better than using a service bus, or reducer pattern, or even multiple stores.

    There are a few other negative side effects of these patterns as well in terms of re-render, but that’s really only an issue with a *lot* in the components/app. CRUD apps should be okay.

    Reply
  6. Sebastien Lorber

    Hey,

    I’ve been using React this way from React 0.5.2 more than 2 years ago.

    The result is that it does not perform so well even with very agressive shouldComponentUpdate everywhere (particularly on text inputs on mobile devices), and is very hard to maintain as props have to be passed down to a lot of intermediate components (unlike Redux’s connect())

    Also you won’t be able to use ReactRouter easily with this approach as RR does not like the routing code to render everytime, and it’s generally placed at the root of your app so…

    For these reasons we are migrating to Redux.

    For some details of our legacy stack: https://stackoverflow.com/questions/25791034/om-but-in-javascript

    Reply
  7. Lars Jeppesen

    How come RxJS, Redux etc are considered part of the “React Ecosystem” for you, when they are totally independent libraries?

    For example; I’m using Redux and RxJS in my Angular2 applications, without any problems…

    Reply

Leave a Reply

Your email address will not be published.