4 ms·
Hooks are not magic, they just increment an index that's used to access the state, which is saved in an array. So you have to call the same hooks at the same ti
by throwAGIway 2y ago
Hooks are not magic, they just increment an index that's used to access the state, which is saved in an array. So you have to call the same hooks at the same time on each component function call. That's it, zero magic.
Async and await is not useful for React, Suspense is done out of necessity. It's have to be generators if anything - but Suspense is older than wide support of generators and async/await too.
You don't have to use hooks nor Suspense at all - just use your own state management and pass it as a prop at the root render call, or no state at all.
- moritzwarhier 2y agoI'm not sure why you're being downvoted. Your comment might not bring much new information, but I agree with what you say. Except maybe for one thing: "state" is about observability. That's what justifies the "rules of hooks", not the inplementation details. I guess people call hooks magic because they look simpler than they are, as if their implementation would rely primarily on closures, which is not the case. But they play very well with reasoning about closure scoped inside of React components. Regarding async/await, I'm really pretty much torn. Why can't Suspense wait for any Promise to resolve? Or has this changed by now? Regarding conditional hook calls: this is really a non-issue in practise, it sometimes even helps with code quality. Would you initialize an observable property of an object/component conditionally? I think I'd prefer to avoid that as well.
- acemarke 2y agoConceptually, a `<Suspense>` component acts like a `try` boundary around a given component subtree. If _any_ component inside of that subtree suspends, it needs to "bubble up" to the ancestor `<Suspense>`. But, React implements the core component tree rendering logic via a single `while` loop that iterates downwards. That's flat, logic-wise, whereas the tree is nested. Meanwhile, React already had similar behavior for its error boundaries, where a thrown error in a component would get caught by the component rendering logic and it would "bubble up" to the nearest error boundary. So, they opted to implement Suspense's mechanics the same way, except that instead of throwing an error, you throw a `Promise`. Meanwhile, React components on the client have always been pure synchronous functions. No `async/await`, no generators, and thus no support for returning a promise from a function. With React Server Components, React now supports `async function` components _on the server only_. They've done some prototyping with support async components on the client (and I think even briefly accidentally had a couple releases where that technically was turned on), but there's some kind of either technical issue or release planning issue that's kept them from building out that support for client components (possibly support for `AsyncContext` in browsers).
- moritzwarhier 2y agoThanks for your explanation. What I don't understand is why there's no easy way to conditionally render a subtree depending on promises contained in the props of the top-level component. Kind of like a guardian HOC triggering a re-render whenever all props promises resolve. Is it because that would be a one-time thing and not synchronously reactive? Seems like people reimplement or reuse this kind of thing all the time, but often using useEffect (transforming side-effects into state, losing deterministic rendering). You're right that this would get ugly really fast with nested suspense boundaries though.
- acemarke 2y agoHmm. Trying to understand what you're suggesting here. You can pass _any_ JS value as a prop to a child component, and there's nothing special about any of that as far as React is concerned. You can pass a primitive, an object, a Promise, an AbortController, a DOM node, anything. All React cares about is "here's what gets passed into the child". Suspense has a couple key bits of behavior: it needs to let _deeply_ nested components trigger the _nearest_ Suspense boundary, and it also needs a way for React to know when the async behavior is done (ie, the promise resolves) so that it knows when to re-render. Throwing a promise is certainly unusual conceptually, but makes sense in light of those constraints. (I'm probably misunderstanding what you're envisioning and didn't manage to answer it properly - feel free to clarify with an example if you'd like!)
- moritzwarhier 2y agoYes I'm not sure myself if what I say makes sense when thinking it through again. I was thinking something like <WithPromises asyncProp={promise} asyncProp2={promise2}> {(resolvedValues) => children(resolvedValues)} </WithPromises> But that's already possible to do yourself I guess, but better in different way. E.g. not using a function prop as "children" etc. Many libraries also provide nice and clean interfaces to provide async state (e.g. react-query) and then of course there's good old useEffect. I think I see why they use a different approach. Guess I'd mainly just love a streamlined API for data fetching built into the core. Right now it's a lot better to just have async code outside of react change prop values. Love the section on client data fetching in the react docs though.
- chuckadams 2y ago> Hooks are not magic, they just increment an index I think it’s reasonable to wonder why they’re implemented in such a strange and brittle manner as that. Vue needs no such weirdness, the composition api just uses normal scoping in JS.
- anonymoushn 2y agoGenerators were in every browser in early 2017 and Suspense was in late 2017?
- throwAGIway 2y agoLatest version of every browser, not every browser version I was forced to support because it was still used by a big chunk of users...