Hacker News new | ask | show | jobs
by desc 2247 days ago
I'm going to express something a lot of people are thinking and are being far too diplomatic about.

React Hooks are a fucking stupid idea and always were.

They're basically just adding dynamic scoping to a language and framework which doesn't need it, in one of the most 'magical' and confusing ways possible. You have to care about execution order to understand exactly how they'll all work and that will bite you eventually if you're building anything without knowledge of all the contexts in which it's called.

There's a reason that most languages stick to lexical scoping: you can see the dependencies, in the same file.

And a large portion of the value of functional languages is that they avoid state, making magic-at-a-distance impossible.

Boilerplate is not the problem. Magic is the problem.

9 comments

I for one preferred the diplomacy.

The 'magic' involved in hooks is a tradeoff; there are real benefits in the way you can consolidate logic, or mix in behaviors. Personally, I strongly prefer hooks to HOCs.

Many technologies have magical behaviors and are still very popular and useful (Rails comes to mind). I'm really liking the pros and cons being brought up in the rest of this thread.

> Personally, I strongly prefer hooks to HOCs.

To me this is the most visible win. useSelector for Redux, useIntl for react-intl, useHistory for react-router, useStyles for material-ui, etc. Almost every library I use radically simplified their API by adopting hooks.

It also makes types much easier to analyze (whether using, say, VSCode's inference or Typescript) when using hooks. With HOCs, types tended to get lost through the arbitrary amount of <Wrapped {...props} /> chains.
This. So much this. You are 100% correct. Hooks are incredibly stupid. No, your component is not "functional" because you don't use the word "this". You still have a "this", it's just fucking secret now so your debugging is harder. I could go on about all the other reasons hooks are stupid, but JavaScript is largely a cargo cult and I'm a nobody so I'd just be wasting my breath.
I honestly don't know how hooks work that well but I find them easier in general to make quick reusable stuff or just plug things in without having to worry about layers deep of Higher order components. There used to be class = logic , pure function = takes data and outputs jsx. But now functional components manage their own state and somehow trigger rerenders of themselves (how do they do this btw?). So they don't really seem to be 'functional' in the functional programming sense, but more the 'we use the function syntax of JS' sense. I don't know haha.
> don't really seem to be 'functional' in the functional programming sense, but more the 'we use the function syntax of JS' sense

That's right. A true function has referential transparency. I get that hooks have ergonomic benefits in some situations, but I wish people wouldn't call them "functional".

Who is saying that, the point of hooks is composability.
How objects work: there's a lookup table for methods and properties associated with your instance to find them by name (or call signature, or whatever).

How hooks work: there's a lookup array associated with your instance (yes, an instance of an object—read the code if you're skeptical, and besides functions are objects in JS anyway so even if I'm wrong, which I'm not, I'm technically right) to find properties and methods by reference order(!?!)

Hooks are just a crippled implementation of objects with weird syntax. In a language that already has non-crippled ones built in with less-weird syntax.

So, closures are indeed poor man's objects.
It's storing all the hooked-in functions (methods) and variables (properties) outside the function and associating them with the relevant React view object instance at run-time, which is effectively the hooks' "this". Unless the code's change substantially and in very fundamental ways since it was introduced. It doesn't bring to mind closures, at least as I read it. It very much brings to mind object/class-system implementations.
I don't think FP purity is the point of hooks, the advantage is the ability to compose them, more easily than HOCs.
Hooks are an elegant & clever idea but they can be difficult to use in practice. You really need to understand in detail how closures work. Manually managing your dependency graph and memoizing in all the right places is harder than the old class-based model in my experience.

I've really enjoyed working with React but it seems to me like some of the newer frameworks like Svelte have taken the best ideas from React without the baggage.

> Manually managing your dependency graph and memoizing in all the right places

I wouldn't say it's harder, but it's certainly not simple. There are a handful of mistakes that I see repeated, but if you get over those hurdles, you can significantly simplify your components 99% of the time. It was very easy to have huge componentDidMount and componentDidUpdate methods in class components, and with logic scatter shot across a big file without the ability to easily reuse bits of it.

I converted a medium-sized React codebase from classes to hooks. In most cases it simplified the components and eliminated boilerplate. But it also introduced more than a few very tricky bugs and serious performance regressions that were not trivial to fix.
I have yet to see any real code that got simpler from hooks.

I have classes that need to do stuff when they appear on screen and that need to clean up when they are unmounted. They also have some local state.

Hooks make doing all that messier. Class components make doing it easy to read and sensible.

React team made the wrong separation of problems with hooks.

Class component should lose its ability to render and replace it with attach functional renderer. In its place, class component should have composable and detachable state and substates with their own lifecycle, each communicating via events within the same context.

It will be truer to `ui = fn(state)` principle.

This is a result of contemplation after learning what the functional people and rust community are doing, and then coming back in front of my laptop showing my professional project in React and TypeScript.

Unstated (https://github.com/jamiebuilds/unstated) is a library that helps scoping and lifecycle separation.

I used in-house event library but there are a couple of libs out there providing this functionality like https://github.com/KeesCBakker/Strongly-Typed-Events-for-Typ...

It took me months to experiment and reach the decision which finally helps the team to write and iterate faster. I hope this will help everyone facing those React problems.

The knowledge-gap of closures is simply an indication that you need to solidify your Javascript foundation prior to understanding hooks. I only see this as a benefit.
well, from my point of view i write 40% the amount of code with react hooks than i did with react classes, i probably reused about 40% more code, and can write components 50% faster than before. i also refer to React documentation about half as much as before.

not sure what's 'fucking stupid' about that.

it might be harder to grok at first - but that's the reality of tools - by nature, they get more complex but more elegant, i think it's fucking stupid to want to go back to componentDidMount()componentDidUpdate, componentWillReceiveProps, componentWillUnmount, getDerivedStateFromProps and UNSAFE_componentWillUpdate. like... really?

I never understood what was wrong with class components anyways. What did Hooks bring that couldn't be done in an easier to understand way with class components?
Hooks allow for declarative behaviour that’s harder to model other ways. With hooks, something like declaring when an event listener should be in play becomes much cleaner. The alternatives with class components are messy.

The above criticism that you don’t get to have pure functional components anymore doesn’t really make sense to me - either you have some lingering state to deal with, or you write a pure function. Your hand is forced by the problem. You could switch over to class components but they’re really not much clearer to read.

Most of the bugs I’ve seen have been around JavaScript’s crummy equality checks and the need for more memoisation.

The problem wasn't the fact that components were classes. The problems were the React lifecycle methods. People did some crazy things with instance variables, shouldComponentUpdate, and componentDidUpdate, and especially the deprecated componentWillReceiveProps.
very much this. I had to refuse a lot of code because developers were using these in inconsistent, confusing, and actually incorrect (read buggy) ways.
I found class components really....wordy binding this to this all the time and lifecycle methods could become kinda wild after a while doing all these checks for a bunch of things...and imo that just trended towards these bulky spaghetti class components / lifecycle methods.
I think the use of classes was just some sugar to help OO and Java people (like myself) into React components. They're not really classes in the useful sense, and I found my components littered with functions that returned blobs of JSX that felt too small to be factored into full "classes".

Smaller function components and then adding state with `useState` has simplified my code.

The React team’s claim (see my other comment) has been that by using React at all, you are already fully bought into all this magic, it’s just harder to tell.
Absolutely 100% agree; Reactive programming environments become easily unmanageable unless they are Functional Reactive Programming for the exact reason you state. Hooks are a way to manage that complexity from an imperative perspective with the illusion of a declarative veneer.
I don't really understand this. Can you explain how hooks relate to dynamic scoping?
Basically every call of a functional component MyComponent() represents a new scope. When you're working with a hook such as useEffect(), you have to pay attention to the dynamic scope so and correctly trigger the useEffect() with the dependency array.
But everything in that dependency array is from lexical scope, correct? Your hooks execute in the scope of that call to MyComponent().
Correct, but the callback passed to useEffect is only scoped to the call stack triggered by the dependency array. So, this makes the callback passed to useEffect dynamically scoped.
The callback is always defined it's just not always invoked. Otherwise it's a regular lexical closure like any callback in JavaScript. Maybe I'm not understanding what you mean by dynamic scope.
Yes, it's just regular lexical closure. I don't think his comment was literal since JS itself is lexically scoped. If you think about how `this` works in JS, it's very similar to dynamic scoping because it matters where the function is called.

With hooks and dependency arrays, similar to dynamically scoped languages, it matters where the function is called.

Thing is, people rarely write functions and use `this` is in dynamically scoped ways anymore. Remember when you had to explicitly bind function scope everywhere? With ES6 and arrow functions, I don't miss that.

Anyway, hooks forces you into that mindset now.

Yes. Except mobx ;) I like to use function components together with class based observable view models. Only a single hook wires them up. Works like a charm and avoids all the IMHO confusing hook complexity.
Hmm, so the reusable bit is the straightforward inject-everything component, driven by an app-specific, app-aware hook-using part?

I can see how that can work for simple cases. Nesting components is going to get tricky though if the classes don't operate exactly the way the hooks expect.

Of course that's the problem: someone built hooks for their trivial cases and now they're the 'preferred' approach...

Edit: To clarify, 'simple' is going to be context-dependent since hook behaviour is. If your 'driving skeleton' of hook-based components is in the direct uninterrupted ancestry chain of every class component, you're probably using hooks in a near-ideal case.

This is how we use it: https://imgur.com/a/I6BD4vF To be honest, we abstracted hooks away.

It works for arbitrary complex cases.