React: A Programmer’s Lament
By: Blake Miner
Tags: JavaScript, React, Redux, Riotjs

React: Lamentations

Photo by Sydney Sims on Unsplash

Alas, despite React’s best intentions, sometimes the darn thing just gets in the way of getting your job done. These are the things about React that keep me awake at night…

#1 — Immutability is overrated and annoying. Without a helper library like Immutable.js, ensuring immutability is a pain.

Everything in life has a purpose, and in my most humble opinion, immutable state is not something you want in a JavaScript UI library. I’ll make a feeble attempt to prove my point with some code.

Have you ever seen code like this?

this.setState(state => ({

projects: {

...state.projects,

tasks: {

...state.projects.tasks,

[id]: value,

},

}

});

Of course you have. So much copying of state! The above is only 2 layers of nesting… I’ve seen much, much worse in practice.

How about code like the following where the developer got lazy? You’ve never done this, right?

const { tasks } = this.state;

tasks.push(newTask); // I modified `this.state` directly! Buahaha!

this.setState({ tasks });

Curse you immutability! You make me work harder to fix others’ mistakes. You make me think about tries and other immutable-friendly data structures… things most developers don’t understand. My heart overflows with sorrow and grief!

The “Truth”: If React wanted to stress immutable state so badly, they should have included a mechanism (like Immutable.js) directly into React, not as a separate library.

At least one other person has experienced similar pains. OK, at least two other people.

#2 — There is extra friction keeping track of component life-cycle methods, what you’re allowed and not allowed to do inside of them, what they’re even called, and knowing which ones are deprecated (and why)

What are these names? componentDidMount, componentWillMount, componentWillReceiveProps, getDerivedStateFromProps, shouldComponentUpdate, componentWillUnmount…

Also, componentWillReceiveProps is deprecated. Hey! I was using that!

So much sorrow… my heart overflows with sadness and despair…

#3 — There is no good way to synchronize state between a parent and child component

… and React goes a step farther to strongly discourage such state copying in the first place.

Consider a form component populated with initial state from one of its ancestors. We want to mutate the state of the unsaved form internally in the form component itself (without changing the state of the ancestor component until the form is saved). This implies a need to copy the “global” or “committed” form state from the ancestor to the form’s “local” state when the form is initialized, mutating the “local” state as the user changes data in the form, and then updating the “global” state when the form is submitted or saved. This use case is common, but the solution in React is not the most intuitive. Allow me to elaborate…

In React, there are two types of components: controlled and uncontrolled. A controlled component is one that bases its state off of the parent component; whereas, an uncontrolled component doesn’t. State copied from the parent to the child is called derived state, and React strongly discourages developers from doing this, instead wishing to maintain a “single source of truth” for application state. Oddly enough, one recommended way to solve this problem is to have the ancestor component maintain the “real” and “draft” states and design the form component to be a controlled component.

If you really want the form component to maintain its own “draft” state, as it probably should to avoid polluting the namespace of higher order components, React provides some recommendations; although, none of them are fantastic. I’d strongly suggest that any React developer read the linked article to examine these recommendations in detail.

The primary recommendation for keeping the “draft” state in the form component is to initialize it once in the constructor, copying it from the props . Then, in the parent component, utilize the key attribute to re-create the entire form component when the key changes. While I have used this approach myself to do things the “React way,” this feels like a hack. It can also be slow to re-create an entire component. Furthermore, it can be difficult at times to compute a reasonable key in the first place; it just might involve concatenating a bunch of fields together.

A side-effect of all of this complexity is that the highest-order React component (i.e. App.js) often ends up maintaining the state of the entire web application, including the “draft” state of every form. Organizing all of this namespace pollution and passing down state from one component to the next can become cumbersome as the application grows.

This brings us to Redux. Since managing state in React tends to devolve into managing a single state store, Redux comes in to save the day! Or… does it?

#4 — Redux is not the solution to our state management problems

I want to like Redux, but after using it in fairly large web applications, I’ve found that it just adds even more friction to state management.

Yes, time travel debugging is cute and trendy. I’ll give you that! But… allow me to lament!

In my opinion, Redux is the medication to treat a symptom of React — passing state down to descendants via props gets repetitive and annoying. Redux merely covers up this problem; it doesn’t solve it. Sure, you might not have to pass props down from great, great grandparent components; instead, you’ll have innumerable reducer functions.

Aside from that, there are small, annoying “gotchas” when writing reducers… like forgetting a break in a switch statement, misspelling/recalling the action name, etc.

#5— this.setState(…) delays and queues state updates until the next tick

This is done for performance reasons, but it can also be a source of unexpected bugs when various components call setState multiple times, potentially clobbering each others’ updates to the state. Maybe you’ve never seen this before? You will one day…

#6 — React locks you into using some tools, some of which are heavy and slow

Take JSX for example. Because JSX is not valid JavaScript syntax, you need Babel. You can’t use React without it, really. This is acceptable but not ideal.

I ask myself these questions often… What happened to cool HTML template engines like Pug? (answer: this Babel plugin) Why is Babel so slow and fat? Why does it take several seconds for my application to compile? (answer: probably Babel) Why do I have to use / configure webpack? Why do I need to transpile my code again? (answer: you probably don’t; it’s 2019)

I suppose at this point I am nit-picking… but being “tooling friendly” is a serious selling point when it comes to software libraries. My point is that React offers lackluster encouragement in this category.