And they just handwave it away in the docs with an imaginary future API. Embarrassing.
And they just handwave it away in the docs with an imaginary future API. Embarrassing.
Honestly, by the time class components are fully deprecated, you'll probably be able to ask ChatGPT to rewrite your codebase to use hooks instead...
Add a router like react-router and you have another 20 apis.
Add on top of that a state manager, even small ones, and you're adding another 10/15 apis.
The world has embraced hooks, for better or worse. The fact is React still works well, and the React team has always been clear that performance is (somewhat) an implementation details. For example, they often advertise to use inlined functions and to use `useCallback` only if you're facing performance issues.
So `useCallback` invalidation is definitely not "fundamental" (imho)
I hope the irony of criticising a team for launching something early, on HN _of all places_ isn’t lost on you.
> going all in on hooks
They went all in on hooks in 2018 when they rewrote the internals with React Fiber.
Hooks are how react works, class components are more of an abstraction—not less.
> while the fundamental problem with them, which was raised in 2018 is still unsolved
If it was a showstopper it would have been fixed by now.
export function useMemoOne<T>(valueProducer: () => T, deps: unknown[]) {
const [initialValue] = useState(valueProducer);
const memoizedValue = useRef(initialValue);
const memoizedDeps = useRef(deps);
if (
deps.length != memoizedDeps.current!.length ||
_.zip(deps, memoizedDeps.current!).some(
([dep, memoizedDep]) => dep != memoizedDep
)
) {
memoizedValue.current = valueProducer();
memoizedDeps.current = deps;
}
return memoizedValue.current;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useCallbackOne<T extends (...args: any[]) => unknown>(
fn: T,
deps: unknown[]
) {
return useMemoOne(() => fn, deps);
}
FWIW, for this and other reasons, I've recently been looking into "actually reactive" frameworks (like Solid/Svelte), and I think I vastly prefer their paradigm to react's.Specifically, I used sycamore-rs to build a Rust/WASM UI, and it's great (once you get the hang of dealing with lifetimes in sycamore).
https://github.com/reactjs/rfcs/blob/useevent/text/0000-usee...
- To avoid re-triggering stuff from Effects, we _are_ adding a Hook. It has the same API as the original `useEvent` proposal but is more tightly scoped to this particular use case (and different semantics). We'll submit a new RFC for it after some more testing, but it's available (as `useEffectEvent`) in experimental builds, and the docs mention it: https://react.dev/learn/separating-events-from-effects#decla...
- To improve rendering performance, we are working on an automatic compiler that analyzes your code and makes rendering much more granular. It's still in active R&D but we hope to share an update on it soon.
We think these two things together will likely be able to mostly address the issue. If not, we'll look at the specific gaps and work on them more closely.
In general though, people have shipped huge apps with Hooks, and it's definitely production-ready. Always an opportunity for improvement, sure.
And what do we get from the React team? After five years? More vague promises. More RFCs. More blog posts. More tweets. More docs mentioning nonexistent APIs. No actual shipped solutions. Forgive me, but I just don’t believe you anymore at this point. Five years.
You’re obviously a bit frustrated but as someone who has done a lot of React over the years I don’t quite get what you’re running into that would make you that upset.
I think it's fair criticism we've been slow on shipping this Hook. We try to take a very cautious approach to introducing new APIs, so we are currently testing it in our internal codebase. Once we feel confident the approach makes sense, we will make a stable release with it.
There’s always a lot of complaining about hooks but honestly I think they’re generally pretty wonderful. Occasionally you need to do some gymnastics but more recently I’ve lent on the useEvent pattern to detach reactive and non-reactive code and it’s working nicely.
Is there something I should know about useEffectEvent vs useEvent? We use the pretend / poly fill version and I understand it behaves slightly differently. But in general we just want a function that doesn’t change that always calls the latest version of the real function. Mostly we’re passing it down to other components that use it as an event handler or sometimes in a useEffect themselves.
As ever, thanks for the work on React.
Yea. It still proxies to the latest version, but it doesn't give you a stable function. (In fact, it always gives you a new one in DEV to avoid depending on its identity.) Instead, the _linter_ lets you (actually, forces you) to omit it from dependencies. Conceptually, it's a non-reactive piece of code. There's also a new limitation that you're supposed to only call it but not pass around (also enforced by the linter). There's a bunch of reasons for this design which we'll write up in the RFC. (TLDR: you only really know whether something should be reactive or not next to the actual callsite.)
Though, in our case we use mobx so a lot of our components are effectively memoised on shallow prop comparison. Here the ergonomics of stable function identity work well at a higher level in the stack. You can avoid rerendering large parts of the tree if your event handlers are stable.
I’m a little worried that the api you describe will work against us here (and I’m also generally a little concerned that the smarter compiler stuff you guys are working on might not play as nice with a mobx world - but maybe that’s not true).
Alas, since it's being "promoted" in the React doc examples I'm guessing I haven't seen the last of this :|
On-topic: I read all that and have no clue what useEffectEvent does. Why can't I just use a closure? Passing the effect event is listed as a limitation, but it's never explained why it can't be passed. What happens?
> * Only call them from inside Effects.
> * Never pass them to other components or Hooks.
(https://react.dev/learn/separating-events-from-effects#limit...)
The original proposal was more about `events` this one is about effects.
According to these rules, i can't do
const x = useEffectEvent((event) => console.log("abc"))
return <MyCustmButton onClick={x}/>
Why isn't it allowed?If these are passed down you’re invisibly breaking the reactivity of the child components. It’ll be too easy to shoot yourself in the foot in a child component by assuming that that you can use one of these in your own useEffect.
Like, do you know if there is actual example somewhere, where things break? They (react devs) only talk about something concurrent, but no actual code, only hand waving potential dangers.
Hooks has made me 3-4x more productive. If a technicality is stopping you from enjoying it, that is such a shame.