Photo by Juanjo Jaramillo / Unsplash

React: A Mess That Shouldn't Exist in Web Development

frontend Mar 9, 2024

Rant

This post is about reason of me HATING React. I’ve been using React quite a while, starting from the transition of Class Component to Functional Component on version 16. Additionally, I coded my first React as a curious kid fascinated by React for a very simple reason:

The logo looks very cool!

I truly mean it; the logo is super cool. However, along the way as I use React, I often think that there is something wrong: ‘This doesn’t seem right’, ‘Why is this needed?’, ‘Why people are loving this?’, ‘is this going to the right way?’, ‘Isn’t this more complicated than just JavaScript’, etc. At this point, I consider that React only adds a burden to web development.

So Why Do I Hate React?

Huge Community & Ecosystem

Wait, isn’t it great that React have a huge community?NO, Not at all

After quite a while using React and creating a few wrapper libraries for my own needs, I have a thought that the huge community of React are actually becomes an indicator of the biggest React problem.

React isn’t compatible with Vanilla JavaScript

Most of frontend libraries are made with Vanilla JS. An example of library that you might frequently use is ”Chart.js”. But React is not compatible with Chart.js so here it comes ”React-chartjs-2” A wrapper library to work with Chart.js in React ecosystem. Oh you want to use ”three.js” for some cool 3D? you will need ”React-three/fiber”. In my case, I need to implement ”telegram-web-app”, not so fast, I have to create my own wrapper to be able to use it.

Can you see where this is going? The large ecosystem of React is formed because of React low compatibility with vanilla JavaScript libraries. React ecosystem is big because to use it you must make a library wrapper to be able to use it on React. It’s not an organic ecosystem that a new idea appear or something new created. By using React, we unknowingly increase the complexity of the web development. The thing that might be more frustrating is that, it was so hard to keep maintaining the dependecies library to keep up to date. Eventally you will be faced with breaking change that you cannot longger update your dependencies or you have to break down the codebase to fix that obviously take your time.

some meme
some unrelated meme

Reactive Component

export default function App() {
  const [text, setText] = useState("")

  console.log("rendered")

  return <input type="text" value={text} onChange={(e) => setText(e.currentTarget.value)} />
}

For everyone who ever dealt with user input (forms) must be familiar with those code above. Then what is the problem? When a user inputs, it will cause this whole component to re-render on EVERY INPUT. Meaning that when someone types “hello world” the component wil be re-rendered 11 times. You can try to run the code and count how many “rendered” will be printed on the console. Wait it becomes 12, How? yes on dev it will be rendered 12 times, I’ll also discuss that below.

Imagine someone unknowingly unleashing heavy computation on a component without useMemo. That computation will run on every component re-render. Not only that, The dependency array in your hooks (useEffect, useMemo, useCallback) are also checked in each re-render. It’s like a double whammy for performance pitfalls! I know that you can avoid this by some technique like what react-hook-form done, but remember thats an extra complexity.


Then whats a better manner to achieve reactivity. Most other framework like SvelteSolidVueAngular already adopt a better and faster way to achieve this reactivity. They are signals. What are signals, it is basically a state. But not like React when a state update it re-render the whole component, signal are built with the observer pattern. They have an observer that subscribe to the signal. Whenever the signal updates, only the one who subscribe to it will update. No need to diff the UI and re-render to perform update like what React’s done by adding the complexity to re-render the component under it.

JSX

Basically, React (JSX) is just a JavaScript file that supports html directly. When doing a conditional rendering, we must use the && operator or ternary operator. Yes this is very subjective, I personaly doesn’t like this. Why? this thing can bea complete mess when managing or simply reading a component with the complex state and condition. Not to mention when someone comes up with the idea of using nested ternaries (oh god, I hate my life 😅 when reading those ternaries).

The Footguns

This is one of the things the most painfull when developing a React app. Infinite re-renders/loop are all too common, often triggered by mistakes in setting up the dependency array in useEffect:

export default function App() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setCount((c) => c + 1)
  }, [count])

  return <p>{count}</p>
}

This snippet might appear harmless to React newcomers, but try to run it, and voilà—an infinite re-render unfolds.

Ok lets try another

export default function App() {
  const [count, setCount] = useState(0)

  setCount((c) => c + 1)

  return <p>{count}</p>
}

Now what? your app will hangs with this code. Whats happening under the hood is that a state is changed on the first render -> triggers a re-render -> the state are changed -> trigger a re-render and so on. Its not even updated on the DOM (it just hangs) because the update process hasn’t been done yet, it keeps recursively calls update until the React throws the infinite re-render error.

Those bug are probably easy to find

But the thing is that when you developing a big project. You would probably use useContext and this where the pain starts. A state change on the context will most likely update a component far away from what are you currently on.


Again this burden doesn’t seem to hapen on other frameworks that uses signals. Lets take an example with Solid

export default function App() {
  const [count, setCount] = createSignal(0)

  setCount((c) => c + 1)

  return <p>{count()}</p>
}

This syntax are nearly the same with React. Try to run it and yup you will get ‘1’. Lets try with the useEffect thing

export default function App() {
  const [count, setCount] = createSignal(0)

  createEffect(() => {
    setCount((c) => c + 1)
  })

  return <p>{count()}</p>
}

It would also display ‘1’. With signal aproach it becomes more predictable than re-running the whole component to update a state.

Strict Mode

What would you think the count would be on the browser when you develop this kind of code?

import { useEffect, useState } from "React"

export default function App() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setCount((c) => c + 1)
  }, [])

  return <p>Rerender Count {count}</p>
}
of course it would be 1

No! it is 2. I can’t get it as well at first. How can it becomes 2? There is a black magic called React Strict Mode that Renders the component twice on development. Why? React need to do this to check if your app is safe and sound.

There are times that I was frustrated why React is calling my API twice and take like 15 minutes thinking it was a bug on my side and in the end realizing that it was the strict mode doing its thing. React create strict mode to patches and prevent you to create footguns like i mention earlier.


Others like Svelte, Solid, Vue doesn’t have this strict mode. They don’t need it because they don’t have those footguns in the first place.

useEffect

Now here’s the devil, useEffect(), the real useFootguns() in React.

This is the most abstraction I hate from React. I don’t know, why people doesn’t find this burden. For me, useEffect is the worst, most dirty and overengineered hooks in React. Keep in mind that this hooks exist to replace React lifecycle method componentWillMount, and componentWillUnmount also componentDidUpdate from class component (before React v16.7). It will be more sense and easier to read if they provide more hooks for those lifecycle like useMount and useUnmount but no, they stay with useEffect that handles completely different lifecycle.

Epilogue

Maybe most of you reading this might have a thought ”React is a library not a framework, of course you have to use other libraries to do other stuff”. I do take React as a framework. I can’t accept the fact that a library making a whole new rendering paradigm and more complex, where that complexity is not existent in the pure JavaScript ecosystem.

Indeed, all points I mentioned are very subjective to me. You can take it as my skill issue. Just to be clear, (unfortunately) I still uses React on my daily basis and in the near future as I mainly work with React. But the current frustrations is enough to make me think that React is heading to the wrong path of web development.

So… is React still worth learning? Yes, but do learn the basic first (HTML, CSS, JavaScript)!

We can’t deny that React is the most popular frontend framework. React is still the best choice for jobs in many companies. If you want to learn web development and wanting to land a job, React is the safest bet.

Beside that, do try other frameworks like Svelte. You will be shocked how much easier web development will be not have to think about React paradigm (VDOM, re-render, hooks, etc). I tried svelte (sveltekit) last year and it’s like the dream of development I looking for. Furthermore, if you wan’t to use pure HTML with the React-like you might like htmx. I personaly love using it and have it on my personal project Stashbin. I migrate all the stack from Next.js to htmxGo and Templ. Well, another blog post I guess.


Thats all my viewpoint of the current React. This article has been in my draft since September 2023 and I realy want to post it before the React v19 released 😅. I do love it when they finaly announce the uses of compiler instead of heavily hinge by the runtime reactivity.

Thank you for reading, See you on another blog post😉!

Tags

Gaung Ramadhan

Software Engineer. Creating bugs and breaking things everyday.