Surviving React

I created this page to document all the times React abuses me. It's my therapy.

1. Using a Set in React is the goofiest, most unexpected code I think I've ever written.

I worked with Vue for about 4 years and so the contrast between Vue and React in this case I think makes it hurt more. Check out the difference:

// Vue
data() {
  return {
    flavors: new Set()
  }
}

// List of flavors will update when you do this
this.flavors.add('vanilla')
this.flavors.delete('vanilla')

Vue lets me update the Set and move on. React makes me dance around and remember how to do it The React Way™:

// React
const [flavors, setFlavors] = useState(new Set())

flavors.add('vanilla') // Add flavor...
const newFlavors = new Set(flavors) // Create new Set...
setFlavors(newFlavors) // Now list of flavors will update

// But if you're good at React, then you know this...shortcut?
setFlavors(prevFlavors => new Set(prevFlavors).add('vanilla'))

flavors.delete('vanilla') // Delete flavor
const newFlavors = new Set(flavors) // Create new Set...
setFlavors(newFlavors) // Now list of flavors will update

// Or this atrocity
setFlavors(prevFlavors => {
  const newFlavors = new Set(prevFlavors)
  newFlavors.delete('vanilla')
  return newFlavors
})

And because it's worth repeating, this is what that looks like in Vue:

this.flavors.add('vanilla')
this.flavors.delete('vanilla')

I feel like we're being trolled here. Sure, I eventually went and learned why, but I do not care. Leaky abstraction and terrible ergonomics. Truly a step backward!

2. HTML inert attribute is unsupported.

React's internals effectively make it the IE of JS frameworks as inert is not the first time React has failed to add support for new web features. And this time they've had more than 3 years to get ready and a fix sitting around in their PRs for a year now.

In practice, this means caniuse.com needs to include React because of how popular React is. Just like IE was! Oooh, foreshadowing.

Anyway, React put developers in this unfortunate situation where it just doesn't really matter if new HTML specs are finalized and all browsers implement them because React is the ultimate gatekeeper. If React doesn't add support you cannot use it, even when all the browsers do! It is exactly the IE dilemma all over again with Facebook being the new Microsoft. They’re aloof, unaware or unconcerned, and really just have a disrespect for web standards and the React developers who have to hack around these avoidable problems. But hey, the docs have a new design.

3. React's janky setup function that isn't

Many components need a place to initialize some variables and do some things just one time for the life of the component. I'm honestly not really sure if this is even possible anymore in React.

Setup is too easy in Vue and other frameworks, but in React you apparently use the totally unintuitive and abstract useEffect hook. Even the name tells me nothing. I'm not looking for an effect...I think. The Effect hook is used to "synchronize a component with an external system", but I need a one time setup function.

Oh, you're supposed to use useEffect and just do some goofy tricks to prevent that function from being called a second time? And more weird stuff to prevent race-conditioning if fetching inside this hook, which is the primary use case for a setup function! Look at this mess:

useEffect(() => {
  // A whole bunch of nonsense just to fetch once...
  async function fetchThingsOnce() {
    setThings(null);
    const things = await fetchThings();
    if (!ignore) {
      setThings(result);
    }
  }

  let ignore = false;
  fetchThingsOnce();
  return () => {
    ignore = true;
  }
}, []) // Empty array implies that it’s a setup function...wut?

Oh, the docs say I'm not really supposed to be fetching in an effect...I need to stop what I'm doing (just tryin' to GET some JSON), create my own custom hook which would "use Effects under the hood" (wait, what?!), and then import and use that instead. Or React suggests I install one of the many "Production-grade React frameworks" that make this easier. Easier?!

Does React really suck so bad that it's strongly suggesting right here in its docs that you install an extra React framework on top of React's 42kb just so you can reliably fetch JSON. Once.

4. React can't do Web Components. Why do you do this to me?!

Because of React's persistent issues with Web Components and how widespread React was at the time they landed, React single-handedly stalled the adoption and progress of Web Components. I have personally tried and mostly failed to use them inside a React app. I've also worked with many teams who wished to include Web Components but didn't because of these issues.

It's just like the situation with IE: when the most popular thing is the broken thing, then the rest of the web suffers.

React even goes dumb when trying to use custom HTML. For the longest time it did not allow it (there we go again, React not respecting web standards), but since React 16 you can. You do have to explicitly add anything your version of React hasn't whitelisted as "valid" HTML to its HTMLAttributes in order for TypeScript to compile though.

React docs used to sort of dismiss Web Components. Then they made it sound more neutral. The new docs simply say, "A future version of React will include more comprehensive support for custom elements." I think custom-elements-everywhere.com has done us all a great service in highlighting those frameworks that are rad enough to work with Web Components and showing us the one - and it's just the one - that is still broken. Thank you custom-elements-everywhere.com guy!

5. Variable scope

A component in React is a function, which means variables defined in the top level of the function will forever prevent you from using these same names in the rest of the component. You'll get the '<variable-name>' is already declared in the upper scope error.

Naming is very important to software craftsmanship. It's hard enough to thoughtfully name things in general. React's design makes it even harder. Shame.

Vue and Riot and Web Components do not have this problem.

6. Inefficient and wasteful is normal

In React it is not considered poor engineering to unnecessarily rerun code. Two examples immediately come to mind: the component render and effect callbacks.

Every time React renders a component the entire component function runs again. This means everything defined in the function is evaluated and rebuilt from scratch, including things that do not change like event handler functions. One thousand renders means rebuilding function myClickHandler(e) {...} one thousand times, even though nothing about that click handler changed. We would never tolerate a non-React function that did this - it would be considered a very poorly written code.

Effect callbacks often do things we need done once, like starting an interval, but that code too gets rebuilt after each render. One thousand renders means creating one thousand intervals, and this is considered reasonable: "There’s nothing wrong with tearing down an effect and setting it up anew, and we shouldn’t avoid that unless we have a good reason."

Basic engineering alone is a good enough reason. Imagine a power generator that constantly shuts down and starts up again so imperceptibly fast to create the effect of always being on and generating a steady stream of electricity. That would work - and would be strangely impressive - but terrible engineering nonetheless. So why do we accept this in software? This approach results in React apps using more memory and battery than other more optimized solutions. There's even a study showing up to 8% higher resource usage with React Native compared to non-React baseline.