Hacker News new | past | comments | ask | show | jobs | submit login

This hasn't been my experience at all. The implementation details even leak into this crazy thing called "the rules of hooks". It looks like a function but it's actually this new thing called a hook. Which state will you get? That depends on whether the reconciler considers this invocation to be a mount or update. Getting the wrong one? Try restructuring your elements or adding or removing a "key" attribute.

People tolerate this because they learned it but I don't think there is anything essentially simple about it.




If you have an understanding of closures, hooks are quite intuitive.


I have a pretty thorough understanding of both. But I can't understand how you'd find them to be similar in any way. Functions forming closures can be called conditionally, in a loop, or even when no react component is even rendering. Most of the complexity of hooks is not addressed at all by closures, and I don't really what part of their behavior is related at all. Maybe just that you can pass a value to one function, and then get it returned from another one.

The standard hooks delegate to a dispatcher, which has access to the current fiber. The current fiber has a linked list of memoizedState for the current work node. It's true that a lot of the functions that eventually service the hook calls do contain closures. But that doesn't seem to grant much insight into how to use them or how they work.

It's like saying "if you have an understanding of loops, the reconciler is quite intuitive". I mean yes, the reconciler uses loops. But the behavior of reconciliation may still be quite mysterious.


I'll try to fix the comment you're replying to:

I think of React components as not closures but coroutines (and hooks are its yield points).

Hooks are implicitly keyed by index which is the most magic-y/surprising part... but I'm sure if you had to manually key them that'd be criticized too (and would be abused to death-by-bugs, so I can see why they went with this design).

If you understand both points above, you understand hooks.

I still fail to get the (usual) criticism of hooks. They have lots of warts but the API (which is often what gets mentioned) is the most superficial and less annoying criticism. Feels like something that would be mentioned after just skimming the docs. Very shallow.

On the contrary: useEffect is a footgun and deserves criticism. useRef being overloaded to mimic instance variables is confusing for newbies (but this is just a naming issue IMO). The difference between normal/layout/insertion effects is complex and subtle. The new stuff that tries to solve some issues with the concurrent mode (like transitions/deferred value/etc.) feels like a huge hack.

React 19 will come with its own warts (actions, RSC...)

But the API? I don't care at all. Pretty simple, at least for my mental model.


The complaint is they prior to hooks react didn't have any of this complexity. It was pretty simple to understand. Class components works mostly as you expected them to. There are a handful of things that were really hard to do with class components that hooks + function components made easier, but lots of other things became more complicated with each new set of features react has added since then. At this point the solution to "class components are complicate" is far more complicated than class components ever were.


I understand the complaint but my point is that, in my experience (which admittedly might be biased), the complaint usually does not resonate with anyone that did actually use React for at least a moderately complex app.

Hook's API is not perfect but it's a good-enough abstraction that allows the user to have even better abstractions and separation of concerns.

Actual React users did not care about that because the pragmatism far outweighs the theoretical ugliness... which honestly is not even that ugly if you have a mental model similar to coroutines (of course if all you do is OOP a class will look better to you...)

I have recently been fixing some stuff in my old React pre-hooks code and I hated it because class-based components had all sorts of concerns intermixed on their lifecycle methods... no matter how much you tried to abstract them.

Abstracting those into reusable hooks was a breeze and made everything much easier to follow and maintain.

Hooks are far better from a pragmatic point of view.

> There are a handful of things that were really hard to do with class components that hooks + function components made easier, but lots of other things became more complicated

Like what? Does not match my experience at all.


> I have recently been fixing some stuff in my old React pre-hooks code and I hated it because class-based components had all sorts of concerns intermixed on their lifecycle methods... no matter how much you tried to abstract them.

The lifecycle concerns are still there, sometimes things need to happen when a component is first displayed, and sometimes things need to happen with a component is removed from the page. It is just handled differently now.

My other issue with hooks vs classes is that, and I say this as someone who loves FP, the best application of OO programming is for UIs. At the base level, a text input field has an object created in the browser, and that input field has state. UIs are inherently stateful things. The OO model is natural for building UIs, you typically shove an object on the screen, that object has some state, and the user manipulates that state.

OO is a great abstraction for UIs. IMHO it is a pretty bad abstraction for most other problem domains, but for stateful UIs, OO maps pretty darn well to what is actually happening!

I just don't see the benefit of throwing another abstraction layer on top of all that.

One reason I love Svelte 4 (haven't tried 5 yet) is that it is so bloody simple compared to React. After years of programming in React, I was literally 5x more productive in my first ever Svelte project than I had ever been in React.

All I need is a good way to encapsulate HTML components for reuse (which is fundamentally an OO thing, instantiate new instances of a component template!) and a good state management system that pushes state changes out to subscribed components.


For me, it's not OOP vs functional. I find "functional" to be a misnomer as its applied to most react components. Functional used to have a clear definition about having a stable return value and only varying based on parameters.

However, the same problem started afflicting class-based components prior to the introduction of hooks. When fibers were created, component instances no longer tracked their own state. State was injected into them prior to invoking the render function, based on reconciliation. I suppose even before that the reconciler was still choosing which component instance to render.

But at least components had an identity that could be addressed in application code. Now component identity is effectively the fiber instance identity (or its alternate), which is impossible to get a reference to.

In real life, people really seem to like hooks. I can see some of the benefits they provide over the OOP class-based component model. But I can't avoid also seeing the mental foot-guns associated with it.

The very first time I every tried to write a react app, I got tripped up by this. I add a component that moves around in a parent component conditionally. So I had `const inner = <Inner />`, and then based on some condition it would be inserted in various places in the rendered parent component.

Of course after much wailing and gnashing of teeth, I learned that this variable declaration wasn't establishing a component identity. It merely creates an "element", even if it has a key attribute. Component identity, as used to retrieve state, is fundamentally tangled up with reconciliation which can only see the final rendered output of a component. No other components can have state, at least as recorded by a hook.

Most people don't seem to have trouble with this, but it's not how I naturally think of things.


Unless my memory is very hazy none of that is related to hooks.

Identity in React has always relied on vDOM structure or `key` which was introduced in React 0.4.0 (July 17 2013, <2 months after initial public release).

> For me, it's not OOP vs functional.

Neither is for me. Note I didn't mention functional once.


> Identity in React has always relied on vDOM structure or `key` which was introduced in React 0.4.0

You are correct. But so, I believe, was I. Apologies for being unclear. vDOM structure is determined based on the return values from render functions. As opposed to the line of code where elements are created. So if you have `const cmp = <Comp />` in a render function, it doesn't have an identity yet. As you note, that will be determined later, sometime after this render function returns. It might not have a state at all. It might even have multiple states. If the current rendering is an update, `cmp` might be a mount instead.

All these determinations are made based on the content (structure and key attributes) of the assembled vDOM and the reconciliation heuristics.

> Note I didn't mention functional once.

I assumed that's the comparison you were drawing with this line. "better" than what?

> of course if all you do is OOP a class will look better to you


> of course if all you do is OOP a class will look better to you...

Funny thing is, I do not know what coroutines are and have zero experience with them. I am object oriented class programmer who never cared about functional programming.

I figured hooks intuitively and straightforwardly when I had to work with react a year ago. Ok, function with state, but it was easy to read them, imagine what they do and maintain the code. It just seemed as an improvement over the old react to me, despite having no coroutines knowledge.


Exactly!

To be honest they're not full-blown coroutines (since the React runtime cannot control when the component progresses) which might be a bit more complex to grasp... but the idea of "yielding control" to someone else (i.e. to React, when calling one of its hooks) is there and as you say it's pretty straightforward.


> because class-based components had all sorts of concerns intermixed on their lifecycle methods

I think this just means your components are too large and try to do too many things. When your components are simple, their concerns are simple enough that they can immediately be understood, even if spread over several lifecycle methods.


Which coroutine gets resumed by a rendering component is determined by reconciliation. Rendered components themselves have no identity aside from the reconciler's algorithm for equality.

For me, this is the root of the problem. Or one of them anyway.

Hooks are keyed by execution order, but also the reconciler's opinion about component identity, which you can only control indirectly. Fortunately it usually does what you want. Unfortunately it's not all the time.

I feel like I do understand hooks, but it's not from reading the docs, and it's not from using them. I know this is not typical but doing those things didn't seem to illuminate much to me. I would still find them doing things I didn't want that I couldn't explain. It was only after reading the source that I feel like I understand the model. And personally, it's not one I would use by choice. I can do it if I'm required by a team I'm on. But for me, there's a mental overhead for "thinking in react". It's not a natural set of constraints for me.


I think that the clearer way to explain hooks is just referencing how they are implemented.

When a function component is called it is called on a "fiber" a stateful representation of the component instance.

This fiber is available to the hook as if it was global variable that is set before the component is rendered.

This is why you cannot call a hook is a setTimeout or a callback to another component: the global fiber is either unset or has a different value.

The other part is that each hook invocation works on a state accessed as sorta fiber.hooks[index++].

So for example you can call hooks in a loop or in conditionals or in synchronous callbacks, but each rendering must be compatible with the first.

Eg

  let s;
  If(Math.random()<0.5) s=useState({});
  else s=useState([])
Should work.

You could also do the same with useEffect if you keep the length/nullness of the dept array.


Ive probably been doing it wrong, but isn't useEffect needed to make any hooks work? useState does nothing most of the time


No. `useState` returns the value of the state, and a setter. It's not about side effects, it's about the value. The returned setter triggers a re-render. That's not nothing.



Hooks have very little to do with closures.

The key aspect of hooks is that what you are doing is something like this:

function(context) { context.useState() }

Where context is a variable that stores all the hook related data, except in reality that variable is a hidden global variable and useState() accesses that variable internally.

Yeah, they added hooks as global functions so that they look like a "cute" DSL. It saves you the effort to type c.useState instead of useState I guess.

The above code is just for illustrative purposes to get the idea across, according to other commenters the internal implementation has changed from what I remember, but the principle is still the same. Global functions demand global state.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: