Introducing react.dev
react.dev
react.dev
React clicked really fast, documentation new and old, articles helped get on track really fast.
Wiring that all together and taming CRA (Create-react-app) with react-app-rewired to add stuff to webpack, like adding libraries to behave like modules that are NOT modules, packing all with scripts into single html, understanding where are boundaries between fluentui and react (both new to me), setting up monorepo because I separate reusable components from app itself and libs, applying css which I am bad at and stuff like that took more time... complexity just explodes, but less "mental effort" overall achieved by having streamlined build, reusable stuff etc.
Otherwise I feel that building app with react takes a lot of "mental effort" away, because you develop a component in isolation which feels simple and when you use that component you don't think about implementation details - it is nicely abstracted away and things just work.
Ah, yes, and I'm lucky I got the signal that functional components are way to go and the only place I had to use a small class component when I want to have a component variant for existing class based component from FluentUi where generic types are important and used within `onRenderItem` and various methods:
> class LookupBaseInternal extends BasePicker<ILookupResultProps, ILookupPropsInternal> {}
> There is currently no way to write an error boundary as a function component. However, you don’t have to write the error boundary class yourself. For example, you can use react-error-boundary instead.
https://react.dev/reference/react/Component#catching-renderi...
You probably want to look at vite for a more direct alternative to CRA. No need for things like -rewired, and it's probably 10x faster than CRA.
No such issues with Vite. It feels super light weight, snappy as hell and can run forever.
Hooks are better because they allow you to reuse component logic, impossible with class components. They solve all the issues and gotcha's of HOC's.
Don't compare class components with hooks, compare HOC's with hooks.
The best way to grow appreciation for hooks is to try wrapping many HOC's and deal with conflicting props and "innerRefs". People seem to very easily forget the horrors of the old.
Also lets be real, hooks aren't that bad. Yes dependencies arrays and useEffect take some getting used to, but I rarely get into situations where I actually get into questionable territory. Use react-query/swr for fetching, use Zustand (or whatever) for state, and you cut out 95% of the problems.
Anyway, the new docs are great, well done, nice work!
What you need for components is a stateful container, with an initialization phase, a render function that can be called for each update, and lifecycle methods that are called at lifecycle events.
Classes give you exactly this:
- Constructor for initialization
- Class fields for state
- A method for rendering that can be called multiple times, which can reference state
- Lifecycle methods
Classes are simple, standard, and give you everything you need for UI components. They were practically invented alongside the concept of UI components.
Hooks try to cram all of this into the repeatedly called render method and it's just a failure.
- You have to put initialization into callbacks because the whole component is called multiple times
- It's difficult to manage object and callback identity across renders requiring useCallback() and useState() to fix
- Lifecycle callbacks are put into hook arguments, but they're just trying to be methods and end up recreating the lifecycle closure every render.
- Hooks have restrictive rules on their usage
- Hooks make a function no longer just a function. It needs to be invoked inside a hook context, making it much less portable than a constructor
- Hooks hide their state within the hooks system, making them much harder to introspect and debug.
Hooks supposedly solve this composition problem and allow reusing logic, but that is entirely possible with classes. It's just a huge amount of FUD and misinformation to say that you can't do composition with objects. All you need is an object interface that is similar to the component interface that the component delegates to from its lifecycle methods. This is a simple and standard pattern to use and implement: it's just a single line of code per lifecycle method. And it's easily introspectable with a debugger: just follow object references to the helper objects.
lit.dev does with with reactive controllers: https://lit.dev/docs/composition/controllers/
It's a very simple alternative to custom hooks, and eliminates the need for all the builtin hooks.
I also find functions much easier to read as there is no "this" and you can simply read them top to bottom for order of execution.
Ah yeah, that does take a while to unpack. I think a lot of the complexity there is dealing with a non-react library and the dynamic import(s). Binding non-react libraries can be a bit rough.
I do think it's a good example to show the big advantage of hooks, if you look at the use of the hook, super clean: https://github.com/graphql/graphiql/blob/50674292c55eadf0e61...
Great way to contain complexity and make usage really clean and simple!
It's a different paradigm, and twisting hooks to behave like classes clearly doesn't work. I partially blame it on React not properly updating their hooks docs in time (they're 2.5y late, ffs).
Hooks are the way to go, class components should have died a fiery death back in 2018, and at this point any other opinion is just wrong.
To your snarky comment, I'll raise my own snarky rant from a while ago, about hooks leading to render loops that are impossible to debug: https://blog.kronis.dev/everything%20is%20broken/modern-reac...
It's probably not the kind of writing that I'd strive for nowadays but the error message not telling me exactly where the problem is felt unacceptable and clicking on the error source leading me to React code instead of the problematic lines in my code felt just unnecessarily weird.
I know that hooks are touted as the best way to extract reusable bits of code, however when multiple useEffect hooks have issues with dependency arrays and start causing render loops, clearly something is wrong.
Personally, I acknowledge that class based components are an easier mental model for many out there, but are also dead due to the direction that React is heading in.
But I would also suggest that the new Vue Composition API does hooks better than React and that using Vue with Pinia (instead of React with Redux) will overcomplicate things way less for developers who just want to get things done.
The situation is calling setState from useEffect, which triggers a render, triggers the setState, render, etc...
If you setState in componentDidUpdate without any guards you will end up with the same loop. I don't remember how debugging this worked, but I'm pretty sure it wasn't pretty.
As to big messy components: I don't think you can blame that on hooks. Hooks make it easier to split all of that up so you have isolated well contained chunks of logic that are much easier to test, debug, and manage.
Vue; not super familiar but I believe they use reactivity right? I think reactivity can be nice, but is also not as great as it's made out to be as it gets tricky as soon as you need to take control and prevent extra renders etc. A lot of it is making diffing and memoization less explicit, which can be nice, but also has some tradeoffs.
[1] https://stackoverflow.com/questions/17776940/javascript-modu...
function* Counter(props) {
const [state, setState] = makeState(0);
const incr = () => setState(state.value + 1)
while (true) {
yield <div onclick={incr}>{ state.value }</div>
}
}Before hooks, if you wanted to call a functional React component, you could reasonably assume it's pure - it depends just on props and nothing else. Only when trying to use a class component did you needed to start to worry about it's internal state and how the lifecycle influences it.
With hooks, you are never sure until you look into the function's implementation!
Small rant ahead. Disclaimer: I find React very useful, just not this constant churn of approaches.
One solution would be to make all state pass through a single point (like Elm or Redux) but some people do not like this because it "limits" them. Limiting side-effects lowers cognitive load, same as Type systems are usually not Turing-complete. Limits can be good.
Same with Context, which is practical because you can write things from anywhere. Congratulations, we were concerned about side-effects and now we are basically using a global variable.
It is like some people talk about side-effects and functional programming and they think it involves using map and filter and done and they don't think deeper about what it means, and see no contradiction in it where it contradicts the principles they are promoting.
What makes the functions look pure? Functions in JavaScript have no guarantees about purity. TypeScript won't let you make such guarantees either, and I think Flow won't either. The only way I could imagine something _looks_ pure is if you have prior expectations of what a function component should be from how they were discussed in very old versions of React before APIs like createRef existed. The preferred terminology from the core team these days is "function components" and has been for a long time. I've been using React since 2014 and they stopped talking about "stateless functional components" well before hooks were announced in 2018.
Classes have lots of rules and best practices to prevent a mess, just like hooks. Also stateful classes are notoriously hard to debug as everything can change everything all the time.
I think you also should try hooks for a bit. Some of your statements about them are incorrect or really not that prevalent in normal use, you might be surprised how nice they can be. Especially use react-query or swr for fetching and something like zustand for state.
I've heard this said before, but I don't understand. Could you give an example of component logic that can't be reused under class components?
You can "sort of" reuse component logic with classes, the most common way is by using Higher order components (see https://legacy.reactjs.org/docs/higher-order- components.html).
The example in that article is pretty good: Subscribing to some external data source. That requires on mount, state, and on unmount.
Using HOC's is not actually reusing logic though, it's creating more components and wrapping them to compose the logic. It's a workaround.
HOC's have a bunch of downsides, imo the most obvious one is the props; you need to pass the previous props, and merge in the new ones. That can create conflicts. Another typical one is refs. You need to be very meticulous about structure and how you compose and order your HOC's to make sure it all works.
In hooks you can define this stateful logic truly disconnected from a component, resolving a lot of the issues of HOC's as you can just call a bunch of functions without impacting the render tree. The "plumbing" is much simpler.
Hope that is sort of clear, but let me know if I should clarify any of this.
For instance, I'm not sure what's meant by needing to merge props. I was under the impression that you could pass a subset of properties as an argument to setState, and all else would remain untouched.
I wasn't thinking of using HOC's. I've done a bit of react, but never touched them. I'm just thinking of reusing logic using the same techniques I use to reuse logic in code. React famously asserts "it's just javascript". Javascript provides a way of reusing logic, namely functions. If I have two class components that have the same logic in them, I'm imagining just putting that code in a function. This is not a sophisticated argument. There might be some reason that wouldn't work, but I don't know what it is.
I think that specifically deals with when using JSX, compared to other templating systems.
I think this might be quite common, if you haven’t experienced HOC hell I can understand the confusion why hooks are necessary.
In terms of putting it in a function: there is only one state object per component, you could pass setState around, but you can imagine what would happen if many functions call it. It’s also a lot more effort on the implementation as you would have to basically pass state, and call do some sort of update in the lifecycle methods. It would probably work for one function, but with many it will get problematic. You would almost have to write your own pseudo hooks framework to make this work in a decent way.
To illustrate statefulness: If you drop a flummy ball on the floor, it will bounce back and doesn't change. If you drop an egg, it will change and you can't drop it easily again. The ball is stateless, the egg is stateful.
Render props (aka. "function as children") are much more free form than HOCs - they allow you to "compose the props" at the site of usage, as opposed to the component implementation.
I don't think there is a use case that HOC covers that cannot be done with render props - please correct me if I'm wrong.
They are imo not ideal as they are hard to memoize because of the render function. It’s funny that if you try to memoize renderprop components you likely have to use some sort of inputs array just like hooks.
Besides the memoization issue it also gets pretty messy if you need to implement many of them. Instead of just putting things in variables you are now nesting many levels deep.
I think renderprops and hooks are functionally not that different, but hooks have better ergonomics when using many.
const { width, height} = useViewport();
return <div>Viewport: {width} {height}</div>
Zooming in, this is just built from a useEffect() to bind event listeners, and and a useState() to hold the current value. Zooming out, it would be easy to write a useBreakpoint() hook that mostly just calls useViewport(). Hooks compose.Before hooks, there weren't great patterns for isolating units of stateful logic from the presentational side of React. A really common pattern was to use "render props" where a component keeps some internal state and passes it into a function-as-child:
<ViewportWatcher>
{({ width, height}) => <div>Viewport: {width} {height}</div>}
</ViewportWatcher>
You'd see these provider-components nested three or four layers deep, and it got ugly. Hooks solved this. class MyComponent {
constructor() {
watchViewPort((x, y) => {
this.something += x + y;
});
}
render() { /* ... */ }
}
watchViewPort(callback) {
addEventListener("resize", (event) => {
// get x and y
callback(x, y);
});
}
This is obviously not production code, and I haven't tried it, so maybe something doesn't work? What is this missing that hooks provide? class MyComponent {
componentWillMount() {
this.stopWatchingViewport = watchViewPort((x, y) => {
this.setState({ something: x + y });
});
}
componentWillUnmount() {
this.stopWatchingViewport();
}
}
watchViewPort(callback) {
const onResize = (event) => {
// get x and y
callback(x, y);
};
addEventListener("resize", onResize);
return () => removeEventListener("resize", onResize);
}
But being able to slice up logic by functionality, rather than by lifecycle event, gets gradually nicer as you have more of it.I think the obvious OO alternative to hooks would have been this:
class MyComponent {
constructor() {
this.attachBehaviour(new MyBehaviour(this));
this.attachBehaviour(new MyOtherBehaviour(this));
}
render() { /* ... */ }
}
class MyBehaviour implements ComponentLifeCycleHooks {
componentDidMount() { /* ... */ }
componentWillUnmount() { /* ... */ }
componentDidUpdate() { /* ... */ }
}
Weird React didn't even seem to consider when they went to hooks. Would be possible to implement yourself though.I'm not saying this is better than hooks / "composable" / "functional" API (I quite like Vue's composable API) but it's less of a departure from class based components.
How do this two behaviours compose together/interact with each other?
> How do this two behaviours compose together/interact with each other?
What?
1. It’s hard to reuse stateful logic between components
2. Complex components become hard to understand
3. Classes confuse both people and machines
See https://legacy.reactjs.org/docs/hooks-intro.html#motivation for details. Agree or not, but it is a very intentional and well motivated direction.
> Agree or not, but it is a very intentional and well motivated direction.
Agree with problem exists (roughly), disagree on solution.
The hook API could be class based:
class ViewportHook {
// API on use
constructor(component) {
this.viewportState = component.addState(this, initialValue)
const unsubscribe = watchViewport((x, y) => this.viewportState.set({something: x + y}))
component.addOnUnmount(unsubscribe);
// you can also use another hook - hook composition works
this.otherHook = component.use(SubHook);
// use the other hook's api
}
// API to expose (in render)
value() {
return this.viewportState.get()
// use this.otherHook too if you like
}
}
You would be able to use it in a component like this class MyComponent
constructor() {
this.viewport = this.use(ViewportHook);
}
render() {
const viewportSize = this.viewport.value();
// use in render
}
}
Boring, and a bit less weird. component.use(SubHook, param)
could be used, that would pass the param as a second argument to the constructor.The main reason why this isn't the case, I think, is concurrent mode. Hooks force certain values to be retreived and stay stable during render (i.e. you can only get a component state value during render function) and this is important if there are multiple setup and teardowns going on.
(Concurrent mode is IMO a bit of unfortunate React complexity that a lot of users of React don't really need, and many others can avoid)
function useViewport(initialValue) {
const [state, setState] = useState(initialValue);
useEffect(() => {
return watchViewport((x, y) => setState(x + y))
}, [])
return state.get();
}
// usage
function MyComponent() {
const viewportSize = useViewport()
// use in render
}
I made a couple of assumptions here. From usage, I assume watchViewport is supposed to both subscribe and return a teardown function. I also assume that the viewportState.set/get are functions for getting and setting the tracked value in the component's state.In my opinion, there are many advantages to the hook version and several major disadvantages to the class version:
1. There's no need for hooks to have a value method or React.Components to grow the use or addState methods because the hook is just a function call which returns a value. How it produces that value is up to the code inside the function--which does call out to React--but the fact the function will always be called when MyComponent is called (absent something throwing earlier in the function body of course). The value you see being returned by the hook will always match the value you get when you call useViewport() in your component. These two things are guaranteed to have the exact same behavior as any other JavaScript function call and thus you can reason about the "registration" and value passing without needing to learn any framework-specific APIs.
2. There's no need for an addOnUnmount method on the component object passed to the ViewportHook constructor, because the hook function can use the function component's equivalent to componentWillUnmount (the return value of a useEffect) in the exact same way as a function component can, but without need for external registration.
3. In order to implement the class API, you'd need to either (A) pass the component's actual instance to the hook, or (B) create a new type of value to represent the instance of the component the hook is registering against. Option (B) is yet another API to learn, as you have to learn a new type of object to deal with in a React application. Option (A) would mean figuring out a way to prevent people from calling those methods after construction, OR introducing the possibility of registering a new slice of state partway through. The latter might be possible, and maybe that's even what you intend, but I'd want to know what the expected impact on methods like shouldComponentUpdate or getDerivedStateFromProps would be. Speaking of those...
4. I can't think of any obvious way you could pass previous versions of hook-related instance properties to lifecycle methods in the same way that you can pass prevProps and prevState.
5. Concision: the hook version has a dramatic reduction in the amount of code you have to read
6. Bundle size: because the hook version relies on functions rather than class properties, it can be minified trivially and thus reduce bundle size even more than the obvious reduction in character count would imply
class ViewportHook {
constructor(component) {
this.viewportState = component.addState(this, initialValue)
component.addEffect(() => {
return watchViewport((x, y) => this.viewportState.set({something: x + y})
})
}
value() { return this.viewportState.get() }
}
The hook version is function useViewport() {
const [state, setState] = useState(initialValue);
useEffect(() => {
return watchViewport((x, y) => setState(x + y))
}, [])
return state;
}
I changed the API to be more similar to the existing useEffect where you can return the unsubscribe function. That makes the difference in the size of code non-significant.Again, its more about API design rather than classes or functions.
Another crucial benefit of the class version: you can use hooks in conditions or loops, in any order. This is because they're not called on every render. I also used a unique `key` to pass to `addState`, but its not really a requirement, since the hook constructors only get called once.
I don't understand points 3 and 4, can you elaborate? I'm assuming that when i call `component.use(HookClass)` the component creates a new instance of that hookclass. Regarding learning, I don't think the whole concept of (functional) hooks and their idiosyncracies are any easier to learn than learning one new type of class.
> Bundle size: because the hook version relies on functions rather than class properties, it can be minified trivially and thus reduce bundle size even more than the obvious reduction in character count would imply
I don't think this will make any meaningful difference. The full component methods API (addState, addEffect) is likely to be in use in any nontrivial project, so that can't be minified away. For 3rd party hook classes, they would be small and independend through `use` and easy to minify / dead-code-eliminate if not in use.
Sure! You show ViewportHook's constructor receiving a "component" prop. Since, in the calling component, you call this.use(ViewportHook), the `use` method is supposed to pass some sort of reference of the calling component into ViewportHook's constructor. My question was about what the type of that parameter is. Internally, is `use` something like `use(Hook) { return new Hook(this); }`? If so, you're passing a direct reference to the class instance. I had thought that maybe the framework could pass a more limited delegate for the instance to the constructor to prevent people from doing something silly with it, but that would prevent you from assigning a property safely anyway.
> I don't think this will make any meaningful difference. The full component methods API (addState, addEffect) is likely to be in use in any nontrivial project, so that can't be minified away. For 3rd party hook classes, they would be small and independend through `use` and easy to minify / dead-code-eliminate if not in use.
Object properties can't be safely minified. Any user-defined component using lifecycles will still need to have "constructor", "render", etc in the generated source, whereas identifiers (like "MyComponent" in `const MyComponent = ...`) aren't accessed by being looked up on an object, and thus can safely be minified to one or two character names. This was one of the motivations of the design of hooks. I believe it's called out in the original talk from React Conf 2018.
[Edit]
And regarding point 4: componentDidUpdate receives prevProps and prevState, and shouldComponentUpdate receives nextProps and nextState. How would you provide information about the previous or next version of that hook-based state if it's being tracked as an instance property rather than in the component's state object?
That seems like a good idea. I'll admit my illustration is not a fully fleshed out design, it was meant more to be a sketch of how it would be possible to make a class based design much more capable than the original one was.
> Any user-defined component using lifecycles will still need to have "constructor", "render", etc in the generated source
Ahh I got it - this is not about DCE but about minified names. Not sure why my mind went with dead code elimination.
My first gut feeling is that this wouldn't be as important for everyday users if they already have code splitting / DCE / gzip. I'll look up the video, would be interesting to see some numbers - I'm hoping thats part of the presentation.
> componentDidUpdate receives prevProps and prevState, and shouldComponentUpdate receives nextProps and nextState. How would you provide information about the previous or next version of that hook-based state if it's being tracked as an instance property rather than in the component's state object?
I'm giving up having a single component state with the new API - this class based hook API also allows you to have multiple states. So I'm going to focus on the props part.
In the hook constructor, you would be able to use a listener for componentUpdate:
component.onUpdate((prevProps, props) => { run code });
You could also do something like component.watch(() => [otherHook.someValue(), this.someState.get()], () => {
// callback
})
to get render-time change detection (equivalent to `useEffect(fn, [otherHookValue, someStateValue])`).Its all a bit wordier, no doubt, but I feel that most of the capabilities can be implemented.
I have to go to bed, but if you'd like to see a much better explanation about why hooks were adopted, I highly recommend the [checks notes] 1,401 comment-long discussion[1] on the PR for adopting the hooks RFC back in 2018, as this sort of design was brought up frequently. Especially worthwhile is Sebastian Markbåge's ending summary about why the team was going with hooks[2].
[1] https://github.com/reactjs/rfcs/pull/68 [2] https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...
(I can't resist commenting I suppose - I did follow that thread as much as time permitted back then, and I couldn't agree with the conclusion from the arguments presented. Specifically, I was unconvinced that dispatch performance and file size were that dramatically different. In my experience, classes are very efficient in modern engines and code-splitting means much more than minification, especially after gzip. Even if they are the right trade-offs for Facebook, its unclear whether they're the right trade-offs for the rest of React users and the community as a whole - from my experience it has errected a bit of a barrier to front end work for backend engineers because there is a steep and weird learning curve at the start)
Copy/paste gets even worse when you fix the bugs in your code: - use `setState` rather than update a prop - handle remove event listener on dismount
That extra code makes copy/paste even worse, so HOCs become tbe better option. Hooks are even better since they don't require passing callbacks into a component to get the data
> const { width, height} = useViewport();
This could be a library call from a component, using normal language constructs. In some languages it would be a service.
React is great, but I wished they stopped changing things every week.
Only new things now are suspense and server components, both completely optional.
React is amazingly stable and largely backward compatible (where it’s not there are usually codemods available to do most of the work).
Redux, mobx, etc also were all options to fix the basic state management in React.
I concede it is not every week. I should have said the preferred approach changes too often for my taste.
This might be controversial but imo the lack of state management in React is a pro not a con. It means the community can innovate instead of being stuck with whatever the framework provides as is standard with most other frameworks. A lean focussed but flexible API.
You can choose the flavor that works for your, patterns like React Query, SWR, Zustand, the Atom based stuff, it's all a big #win imo. I like having options.
Also I'd say changes in preferences are not exclusive to React. React did make the hook change (while still supporting the old!), but otherwise the preferred approaches are not per se exclusive to React.
I would add that there is nothing wrong with sticking to what you are using atm. You don't have to use the shiny new thing if the old works well!
You've forgotten the teardown logic. If the user navigates from & to this component often. You'll add more and more listeners. over time.
The solution would be to store the teardown logic somewhere in a class field and then call it from a destructor.
Now this unit of logic is in three different places of the class. If you need to integrate multiple units, they interleave and the class becomes convoluted. Also it's easy to forget about the teardown.
Hooks can achieve this lifecycle awareness by default. They aren't the only solution though: Vues' refs and Sveltes' stores achieve similar results.
One of the simplest examples is "useToggle" which just sets a state variable to true or false. Not hard to write in a class component but it's still stuff that you may end up writing a lot.
A lot of similar ones are click and event handlers that need to be cleaned up after the component is unmounted.
useIsMounted is one I use a lot too to ensure I'm not trying to write state on an API callback once a component is unmounted.
You could add each of these to a class with an hoc and end up with withToggle(withIsMounted(withUseMediaEvent(MyComponent))) but that tends to be a bit of a mess.
I'm not sure thats really true. In a different world, the react constructor functions would have access to `this.useState` and `this.useEffect` and then you could extract any logic you like by dispatching `yourFunction(this)`. HOC are not the only alternative design.
Hooks really push at the boundary of the language and I'm not sure they're an optimal path once you actually start dealing with state.
How do they push at the boundary? I feel like so many people talk about them being magical, but I don't see how they're any different than any other effectful function call. Is the idea of tracking call order that exotic?
This is a very simplified example of how hooks work under the hood:
let hookStates = [];
let currentHookId = 0;
let firstRun = true;
function endRun() {
firstRun = false;
currentHookId = 0;
}
function useState(initialValue) {
const hookId = currentHookId++;
if (firstRun) {
hookStates[hookId] = initialValue;
}
function setter(nextState) {
hookStates[hookId] = nextState;
}
return [hookStates[hookId], setter]
}
let actions;
function run() {
const [a, setA] = useState(1);
const [b, setB] = useState(2);
// this is done because we're not setting up event handling
// and we need some way to trigger external updates
actions = {setA, setB}
endRun();
return a + b;
}
function assert(received, expected) {
if (received !== expected) {
let error = new Error(`Expected ${expected}, but received ${received}`);
error.name = 'AssertionError'
throw error;
}
}
assert(run(), 3);
actions.setA(3);
assert(run(), 5);
actions.setB(5);
assert(run(), 8);
actions.setA(1)
actions.setB(-1)
assert(run(), 0);
Obviously this example doesn't include tracking more than one component, but as far as I know hook state gets tracked pretty much the same as the state on class component instances.I could understand thinking they're magical if you were under the impression you were the one calling the component functions rather than React, but I think my impression was always that it was still React's job to call the function component (otherwise, createElement seems like a waste). They might have at one point said that it was okay to do that in tests, but I'm not sure about that, and it would have been out the door the moment function components started supporting refs.
`useEffect(f, [])` runs on every render, but only installs the effect once. The other times its not installed. That means standard language facilities such as *closure capture* don't quite work as expected unless you pass them in that list `[]` too
Its not impossible to get used to it, but thinking in hooks is profoundly different than thinking with normal javascript.
I think the original design wanted so badly for state to fully live outside of React, but unfortunately, the information about what components are currently active on the screen is very relevant for what the state should be doing. As such, I think that approach just doesn't work no matter how much we'd like it to work.
Hooks make you run into those potential bugs head-first, and that's always seemed to me like what a large part of the "hooks are hard to reason about" complaints stem from. The other parts are people who expect functions to be invoked in a different way than they are. I don't really know what to tell those people beyond asking them if they've ever seen code calling a component directly.
Its not normal for a render function to run all the time but the closures mentioned in it to be completely ignored. Thats completely the opposite of how most other APIs work in the language. Usually, a (constructor) function runs once, and the closures returned by it may get run multiple times. Completely ignoring the passed closures to hooks in many/most cases is something very specific to React.
React function components are not constructor functions though, so I don't see how those are relevant. Bringing up the expectation that constructors "only run once" in the context of how hooks are hard to reason about seems strange though, as class component constructor functions have to be written in a way where they can potentially run multiple times. I think that's been a requirement for the entire time React has supported JavaScript classes.
What do you mean by "closures returned by a constructor function"? Do you mean the instance and its associated methods? If so, yes, running a constructor obviously produces one instance[1], which is true in React too. But you're never invoking that constructor yourself--and you never should have been doing that with React in the first place--so why would that matter?
Also, I'm confused, can you point at a comparable API to a React function component in the JavaScript language?
[1] well, sort of. Constructors can return any arbitrary object, so the following works
class Foo {
constructor(n) {
return new Number(n);
}
}
let foo = new Foo(1);
foo instanceof Foo; // false
foo instanceof Number; // trueI'm giving an example of whats a normal use case of closures. Running a single function once that creates closures that run zero, one, or multiple times is "normal". Running a single function multiple times that creates closures that run one time and get ignored all other calls of the function is not.
This exactly describes a leading-edge debounce function, which I was asked in multiple interviews early in my career to implement on a whiteboard. I'll admit that "problems people feel like giving to junior devs" isn't a great method of measuring whether or not something is simple, but I think it is indicative of how widely that sort of thing is expected to be understood. It also exactly describes any sort of request framework where request responses are cached after the first call and the result is returned without a network hit in subsequent calls.
My debounce implementation would *not* be trying to catter to some API that looks like this:
render() {
debounce(() => {
justCallCodeHere(); moreLines();
})
debounce(() => {
anotherFunction(); moreLines();
})
}
but more something like this constructor() {
// called only once
this.debouncedFn1 = debounce(() => { justCallCodeHere(); moreLines(); })
this.debouncedFn2 = debounce(() => { anotherFunction(); moreLines(); })
}
render() {
// use the created debounced functions
this.debouncedFn1()
this.debouncedFn2()
}
How would you implement the first version without having access to react hooks? Keep in mind that in every call to debounce, the function object you get as an argument is unique because unique closures get created on every call of render().I hope that illustrates the key weirdness aspect of hooks.
Eventually, React team accepted that it is a problem, and presented hooks as a solution. The very same people who would defend wrapper hell yesterday, would start crapping on it today.
I think you're right that hooks are flawed and not the final answer, but the community won't accept it until "React gods" say so. It would be quite funny if React team eventually decides to go compiler way (a la Svelte) and all the people grasping for reasons to hate Svelte would have to change their opinions again.
The ultimate solution (imo) will come from the functional programming world and it will be a component monad, just it won’t look like that to the end user. It will probably leverage function-star in JS. It will enable unit testing of all component interactions and everyone will say it’s the best thing since sliced bread. Clojure devs will look on wondering why it took so long. Just my hunch!
Anyhow… To this day, I still think it’s an unforgiving, crazy beast of a library for a weekend hobbyist developer like me. But I kinda like it now and in big part it’s thanks to hooks, functional components and these new docs. Good job team, and congratulations on finally getting this live!
Finally, if you combine it with the fact that it seems to hold on as the industry standard for jobs, and being useful for native mobile development via React Native, it all comes together nicely in my mind. With its huge ecosystem and closeness to plain vanilla JS, I guess it just makes me simply a bit of a better developer and gives me a new perspective on things. That's that.
Regarding performance, I remember having blocking sluggishness with v-model that shouldn’t have been there back in Vue 2 days.
Also, for example, in Vue 3, my favorite component library Buefy (based on Bulma) is no longer available, and I don't think any of the other ones have (arguably) come so close and extensive to what I want.
Vue 2's transition to Vue 3 (if you want to use the new CLI tools and the Composition API) had to, by definition, fragment the ecosystem in unpleasant ways no matter how carefully and sensibly it was managed by the core teams in charge.
You can see that Vue went down slightly in recent developer surveys, and I suspect this to be among the main reasons. As if Evan himself would be suddenly more focused on his build tool Vite as opposed to Vue itself (of course it's just an illusion).
But I still love Vue because of their amazing docs, and the fact that they let me enter the world of web-development in a relatively accessible manner (this may not have been possible with React, for me personally). Lack of something like scoped styles alone baffles me in React to this day, but it makes kinda sense in the ecosystem it's operating in...
Worth noting: we've been enabling SSR for React since 2016[1], and some of the largest websites on the internet SSR with Next.js: Twitch, Hulu, Nike, and even parts of Amazon[2]
I had a look at the component libraries that are compatible with Vue 3 a while back and concluded that either PrimeVue or Quasar are some of the better choices for my web dev needs.
Surprisingly, many of the formerly popular options are stuck with just Vue 2 support.
The difference between this and rreact is less typing, easier mental model and less issues (because react calls render() multiple times)
Once you do that, React becomes purely functional (and quite "uni-directional", if that's the right term). Just update the state (in an event handler or when a Promise resolves after a fetch or another part of the UI finishes) and UI auto-magically reflects that on the screen.
Arguably, MobX + React is closer to the React's original ideal of `UI = f(state)` than the vanilla React itself.
I thought Zustand is kinda simplified clone but seems like computed properties don’t work super well.
I'd say in Vue it rarely causes troubles, in React world it leads to few non-obvious edge cases where React's one way data flow is indeed easier to reason about (but less performant).
It has much better Typescript support, and also it doesn't have Vue's spaghetti reactivity system, both of which vastly increase the chances of bugs.
I guess you could say they're more similar if you simply don't care about bugs at all.
Vue 3 has excellent TS support.
Then again, the whole front-end ecosystem is moving at a pace I cannot quite follow. Instead, I've been going into the direction of traditional monoliths and working through the docs of Django and RoR.
There you have a plain "good-old" templating language with sprinkles of htmx and alpine where you need it. This along a bunch of back-end devs that seem to not want to even touch the javascript stuff with a stick. It almost feels refreshing though, particularly if you want to build rapid MVPs while retaining maximum control of your entire stack and IP. When teams grow and you need to support extensive and/or thicker clients, I believe this is where bigger front-end libraries like you mention truly shine. I'm still counting on us coming a full circle and there being a re-emergence of traditional monoliths in the future though. But then again, with supabases, pocketbases, strapis and other "simpler" backends of this world, who knows what'll be happening even in the nearest future...
Side note: I recently took an assessment test on React through Triplebyte and was given an expert level badge (for what it's worth). I think that speaks volumes about the new React docs because that is where I gained most of my React related knowledge.
Having a dedicated place to process changes in props and setup/teardown logic is just generally nicer than weird dependency arrays and returning destructors from effects.
Class components are just (IMO) cleaner, and I find myself saddened at the level of disarray most codebases with hooks are nowadays. Enough has been written elsewhere about how hooks require one to keep more in their head; class components have an agreed up layout/structure/etc that is important in large codebases.
It's just frustrating. Couldn't they have had the decency to fork / rename React for all this and leave the old branch to die instead of turning the entire space into some kind of pedantic war-zone for several years?
IIRC They reported that hooks were a common source of problems.
Visually they look simple but they hide a lot complex and nuanced semantics. Once you start layering and composing it becomes hard to reason about when a value updates, when a re-renders will occur, and when a value even resolves(it may take many re-renders for a value you need to finally get set).
We recommend defining components as functions instead of classes.
Not dead but they are not recommended anymore.
See here for some examples of how you can approach writing the same code in a few different ways -- maybe one of these styles will fit you better: https://react.dev/learn/reusing-logic-with-custom-hooks#ther...
is there a good reason not to uniformly rely on useReducer everywhere in a codebase?
There are downsides to this - you do need to think a lot more about code architecture before implementing the code, and you can get into a really messy/unmaintainable situation if you aren't careful. And you'll need to consider what state your "singleton" is tied to - is it really one per page, one per component, etc? And depending on the answer, the implications may be that this pattern isn't a great idea. But for certain use cases, it can work well.
The core premise of this pattern is separating business and UI logic, which is not exactly a new idea.
[1] https://github.com/algolia/instantsearch
[2] https://github.com/searchkit/searchkit/blob/main/packages/se...
React apps should, in general, contain much less react-specific code than they tend to.
Should make it easier to switch frameworks in the future if the need arises.
Regarding the link, I most likely wouldn't consider routing and bundling to be in that list. Sever integration currently doesn't apply to me, since our app is one of the cases where CSR makes more sense.
In the popular sense, they've been on the down and out for years now.
> It turned in to a bit of a mess until I went for a refactor into classes and everything became much more clear.
in my experience, once the logic starts to become complex, you need to develop custom hooks so that a given component stays readable.
The hooks paradigm is a lot harder once you get past the basics, but I wouldn't go back for reasons I could articulate if there's any interest.
I hate hooks. The syntax feels nice to type, but the issues outweigh the benefits for me. It’s way too difficult to understand the rendering lifecycle, the state updating, the ordering and I also find the reuse abstraction hard to follow (although I accept that might be a concentration/attention issue on my part). Conversely, it’s also way too easy to break the purity of the hook callbacks, so hooks can use state from contexts you wouldn’t expect (more an issue with custom hooks if you don’t also supply linter tooling to go with them).
Why do you prefer them?
Obviously using hooks doesn't mean it has to be shitty, I think it just brought a lot more wannabe React devs that have no experience with architecture or maintainability.
I tried to give it a real shot with hooks, but the marginal benefit they bring seems to be far outweighed by the added complexity. Add to that the mixing of paradigms with some functional components, some class-based, etc and it becomes a mess pretty quickly.
The hook system is the parts of an OO system that React needed to be able to keep developing features & optimizations for function-based components, rather than telling their users & devs that they'd simply have to use classes for some things. It exists so they could side-line class components, rather than having to become outright reliant on them for some features. So, pretty much, yes.
Class components are still the only supported way to create an error boundary. Other than that, they are pretty much dead, yes.
To this day if I need to do something in React I can pick it up quickly because is a very simple model: it follows the template design pattern, which is one of the most powerful and dumb design patterns (I'm talking obviously about the class model).
What's my current issue with React? Well in the same way that they have decided to go with hooks and all that, nothing is preventing those folks tomorrow to wake up to say: "You know what? What we really really need is not functional components but a hybrid approach, not fully functional not fully object oriented" and then the game starts again.
I worked for a very limited time in one company not long ago, and a guy there came and said something like: "We really should start using hooks" and I replied: "Really? Why? Can you show me a true or concrete advantage using the functional style over the OO approach?" then he said: "Well, I don't really need to do that just watch this video" and sent me a link of a video (if I recall correctly) of the creators explaining all the hooks stuff. I didn't even bothered to continue discussing (how can you discuss when someone is 100% biased?).
My point is that many people out there just take for granted whatever ideas these people are pushing out and not questioning things anymore. I'm not saying that hooks are good or bad a thing (I really don't know and if I need to learn hooks because that's the style used in a codebase, sure I'll do it and probably learn something), but blindly embracing things just for nothing is harmful.
So we sat down and actually used hooks for a few days - refactored small and large components to be sure we understood them and saw the difference in the code. We found it to be a perspective change -- once we made that change and got used to the new syntax, we loved it. Our code was simpler and easier to maintain. Once we really learned when to use useEffect so as to not abuse it for everything under the sun... the code got better still.
So while you are correct that blindly embracing hooks would be harmful... Blindly rejecting them is equally harmful.
Having said that, there are some gotchas with hooks that aren't trivial and can make them difficult to use/understand without significant comments. But hey, what code doesn't fit that description?
I understand the "it's dominant and has a large ecosystem" argument, but I just can't wrap my head around how enough people chose it for it to reach this position.
I've evaluated it a couple times and it just looks poorly conceived. Fixes and "improvements" have rolled in, but they are also poorly conceived and themselves need fixes.
Of course I know it might just be me... I didn't get the point of tailwind either, until I used it.
But I've also used react for a reasonable sized project and I still don't get it.
> what do you prefer
Svelte, for the case you mention.
I'm curious what you mean by "declaratively", and I wonder why everyone puts so much value into that particular idea. What is declarative? Is it different/better than using pure functions? Why? If not, why doesn't HTML have (pure) functions?
Pure functions are declarative. It's flexible so you can push it to the point where it's essentially a tortured imperative form, but you're probably doing wrong if you get to that point.
The goal of react and other web UI libraries targeting browsers and web views is to render HTML, but dynamically, based on the immediate application state. HTML isn't dynamic and we naturally want to use Javascript for the dynamic part (due to history and Javascript's great flexibility).
You can use HTML for the HTML and Javascript for the dynamic stuff. A lot of things work this way. But react decided to use Javascript for both the HTML and the dynamic stuff. JSX mitigates this to a degree, but it actually represents XML, not HTML. Thus you use className instead of class, close <input> tags, etc.
Edit: there are a lot of good--and varied--explanations here. Which is why I think the docs should cover it in-depth. It's confusing.
If you were to call them in an "if" branch it would mess up that queue mechanism.
2. React counts the number of executions of (certain) hooks. This count is how it knows which state to get from the store as a return value from `useState`. useState is effectively `getStateAndSetter()` but it doesn't pass a key name of any kind, so the implicitly passed key is `hookCount++`. This is why you can't call hooks conditionally, or state would get all messed up - if a condition turns false and one hook doesn't run that render, all getStateAndSetter calls that run after it will be off by one.
Also, I'm curious if there is any similarity between these React hooks and the infamous Drupal hooks.
Not familiar with Drupal.
React hooks is running on each component, not on whole app. React app is composed by bunch of components, and each of those have their own hooks.
Additionally, this context would also have the context of children elements, so things can actually be persisted across function calls and React can know what needs to be mounted & unmounted.
Also note that because Tagname isn't called directly, it's also how React is able to do its diff and only actually call what is needed.
This is also why if you're generating a dynamic number of elements (ie outputting an array of elements), you should provide a `key` prop, so it can link up the elements to their corresponding past states each time the function runs.
In reality we use a linked list rather than an array. If you wanna dive into the code, I can give some pointers. For example, useState is implemented like this during first render (https://github.com/facebook/react/blob/87c803d1dad7e5fe88634...) and like this during next renders (https://github.com/facebook/react/blob/87c803d1dad7e5fe88634...).
However, _conceptually_ I'd recommend to think of Hook return values similar to "extra inputs" to your function, kind of like extra arguments. There are different ways to formalize it in different languages. We picked plain function calls for simplicity and low overhead, although you could imagine `yield` with generators or something like that.
Why a LinkedList rather than an array?
- React has a module-scoped variable in the hooks implementation file that tracks which component is currently rendering
- The internal "Fiber" data structure that describes a component instance has a linked list of hook contents (saved values and callbacks)
- As each hook gets called, React tracks the current hook entry, looks up the current hook data, and returns it
So, the number of hooks used needs to be the same each time the component renders so that the linked list entries match up consistently.
There's a great talk by @swyx here that builds a miniature hooks implementation in about 30 minutes:
So there is a data structure that store says `[useMemo, useState, useEffeect]` - and when you component re-renders, is unmounting, or has effects to trigger it uses the index order to lookup the bit of state that needed to persist.
This is a pretty good high level explainer of how react works that touches on some of that: https://overreacted.io/react-as-a-ui-runtime/
1) A hook adds a function or variable to one of several lists attached to the component object, when it's instantiated (yes, even your "functional" components are objects, and I don't just mean in the JS-functions-are-actually-objects sense—at least, they were when I read it)
2) Subsequent calls either call those functions, or accesses/modifies the value, in a FIFO manner, reading them out of those lists. This is why you can't mess with hook ordering in e.g. loops.
It's basically just methods and properties on an object, but with FIFO access based on declaration order, instead of using a lookup table of some sort.
[EDIT] A poster correctly pointed out (then deleted their post) that I wrote "loops" where I meant "conditionals". Technically sorta-true (though not quite, as phrased) if the loop isn't the same length every time, but yeah, I meant conditionals. Point is, the reason order matters is that the whole thing's just a bunch of FIFO queues, more or less.
Within the component functions's body, you may have many calls to the same hook, lets take useState() as an example.
Each useState() returns a different state variable, that is kept track of in a cache outside the component function.
On the first render, the state variables are created. On subsequent re-renders, the state variables are read from the cache.
The cache is a simple array. It keeps track of, and identifies each individual state variable from it's index in the cache array.
The first call to useState() gets slot 0 in the array, the second call gets slot 1 and so on and so forth..
For the tracking to work consistently, all calls to useState() within the function's body must also happen consistently.
In the same order, every time. Having a useState call within a conditional "if" branch breaks that consistency.
https://react.dev/learn/updating-objects-in-state
https://megous.com/dl/tmp/zyrzbzjqxccvvdniorpj.webm
(Top of the line desktop AMD chip.)
The code has no transition, no setTimeout, no nothing that would indicate any delay. The dot should always be right under the cursor. This will happen with every similar UI pattern (want to resize something, move a slider, etc. etc.)
I guess this is a result of tradeoffs React has to do by insulating people from DOM.
There's probably some way to force immediate DOM update, but then you're pulling in a lot of useless diffing and checking for each frame, instead of just updating the single el.style.something property you'd do when directly using DOM API and leaving the browser to do the rest.
Slightly off topic but my M1 Pro Apple Silicon Mac does not lag like your top of the line AMD chip.
I know most consumers run React code with CPUs slower than Apple Silicon chips but this post is more of an endorsement for the M1 lineup.
So you just need an M1. The M1 Air is frequently on sale for $800 nowadays. Or if you're really on a budget, you can buy a used M1 Mini on eBay for $400. Very affordable for those who want high browser performance.
Draw a software cursor, and the same exact thing happens.
In fact, in effect, the red dot is a software cursor.
You can accomplish visualizing the same lag in a traditional update-draw loop in game software.
Cursors are special. Sibling comments here don't experience the same effect, because they're running displays at higher refresh rates.
That's it.
I guess you can take your cursor position at the beginning of scanout of the previous frame, render the new browser layer content, tell the compositor to update both cursor layer position, and browser layer at the next scanout, and both should have matching positions on the next frame. Maybe if the compositor is updating the cursor layer position from mouse position known right at the momen of atomic commit, there may be some difference.
Well, this is Xorg I'm using, and it's not using atomic DRM API, so things are not well synchronized.
So this should be `ref.current.style = {x: <whatever>, y: whatever}`. It's also a great use for useImperativeHandle() if it gets any more complicated. This is the example from the docs updated to use a ref: https://codesandbox.io/s/lucid-grothendieck-iquzfj?file=/App...
On this example it pretty much looks the same, which I assume is because of what the sibling comment says, that any cursor drawn in software is going to be slower than in hardware, and the animation and app are trivial so the rerender is basically free. But this way is still better, and it'll be more obviously better if the components being animated are complex. There's not going to be any way to do it faster in JS, but React can be a lot slower depending on what happens on rerenders (which is often, if using imported components, somewhat out of your control).
A similar issue occurs with native scrolling and fixed position elements whose positions need update every frame. You must take control of the scrolling in JS in order to prevent jitter.
Look at your profiler. Notice any significant overhead? You don't.
Implement this same thing using events and `.style` directly. You will get the exact same result.
The pointerEvent => style feedback loop is unfortunately very slow, more or less so in different browsers/operating systems.
Interesting that you would know so little about this and yet feel confident enough to make a proclamation like this is evidence of React's performance.
And they just handwave it away in the docs with an imaginary future API. Embarrassing.
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.
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)
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.
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).
Here's my implementation, for the record: https://pastebin.com/rjj0FDDd
This implementation doesn't account for the fact that the promise may settle after the component has been unmounted.
Even after years of writing fullstack javascript this is my first time seeing AbortSignals, probably because nobody uses them. Some libraries, like Axios, handroll their own cancellation mechanisms, but that does not really cut it.
Hilarious! As someone who was once a "full-stack" web dev in the age of jQuery, CSS resets and local fonts only, this definitely rings true.
And having just started learning React (through Next.js), this is a timely and welcome refresh! Thank you!
This worries me a bit because some React wrappers for ClojureScript expose macros that essentially compile to React.createElement() calls, which are now labelled as a legacy API.
It also looks like Class Components are officially deprecated, given that the documentation explicitly states they are not recommended for use in new code.
We're not removing createElement but I'd recommend to change your wrappers to the same compile output that the new JSX transform (introduced in 2020) uses: https://legacy.reactjs.org/blog/2020/09/22/introducing-the-n.... The new JSX compile target will allow us to do a bunch of optimizations in the future that createElement() can't.
I'll make a note of adding this to the docs.
In our embedded/plugin component scenario where we are given a <div> to load in, it appears we should replace our current pattern ReactDOM.render(React.createElement(... with createRoot(_jsx(....
Yeah you want
const root = createRoot(domNode) root.render(<Stuff />)
(or the JSX transform output equivalent)
So many useMemo, useEffect, useCallback madness was trivial with basic class component lifecycle.
Supporting some features for hooks and classes, both, probably would have meant having two implementations in some cases, that might not quite match up as equivalent. More API surface area, more code to test, more combinations and paths to worry about.
> And aren't classes just really functions under the hood anyways?
Other way around, actually: "functional" components end up represented by an object.
No, they're functions.
Component _types_ are defined as either classes (`class MyComponent extends React.Component`), or functions (`function MyComponent(props) {}`).
Internally, React has a "Fiber" data structure that stores references to that component type for each component instance, as well as a lot of other metadata. That's the _real_ "component tree":
- https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...
In other paradigms, it is! Our paradigm is exploring the functional take. I agree it's a bit unorthodox but we are very intentional about modeling it that way. It really has a bunch of powerful properties one might not expect.
>And aren't classes just really functions under the hood anyways?
The key difference is that in React, UI is a pure projection of current data (props/state). You're always supposed to "return" the UI. Sure a class is a function, but that function is invoked once. Its methods can be called many times, but having a pure render() method (like in class-based React) is really a class cosplaying as a function. Functions are more honest to what React is trying to be.
Relevant part from Seb's Hooks RFC comment (https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...):
> Classes may seem like the ideal thing to hold state since that's what they're designed for. However, React is more written like a declarative function that keeps getting executed over and over to simulate it being reactive. Those two things have an impedence mismatch and that keeps leaking when we think of these as classes.
>Another issue is that classes in JS merge both methods and values on the same namespace. This makes it very hard to make optimizations because sometimes methods behave like static methods and sometimes behave like values that contain functions. The Hooks pattern encourages the use of more statically resolvable calls for helper functions.
>In classes, each method has its own scope. It causes issues like us having to reinvent default props so that we can create a single shared resolved object across those. You also encourage sharing data between those methods using mutable fields on the class since the only shared thing is this. This is also problematic for concurrency.
>Another issue is just that the conceptual mental model for React is just functions calling other functions recursively. There is a lot of value to express it in those terms to help build the correct mental model.
For a concrete example of where classes as a model fails us, consider useTransition (https://react.dev/reference/react/useTransition). It lets you start rendering "in background" with a different state value. But if you get interrupted, the renders have the current value. This highlights that in React, the same piece of state can conceptually be thought of having more than a single value (kind of like being in parallel worlds). Classes don't model that well.
The operative word there is 'exploring'. In practice, hooks are still basically OOP, albeit masquerading as FP. You just shuffled the state into a shadowy realm adjacent to the component where it's harder for developers to see. I know a lot of JS developers are allergic to writing the 'class' keyword (even though JS doesn't really have classes in the first place), and I guess hooks help them sleep easier at night by allowing them to believe that they're writing their code in a functional way. But they usually aren't. And I think that misdirection is the source of a lot of the confusion out there regarding hooks.
"The greatest trick the OOP devil ever pulled was convincing the world that state didn't exist"
Even if we set aside implementation inheritance, class hierarchies, and all that jazz that got associated with modern OOP, fundamentally classical OOP is message passing. React components don't pass messages to each other in that sense. The data flows strictly down. Re-rendering is not message passing but conceptually reevaluating a part of a lazy continuously reactive function call tree.
It's not about "sleeping easier at night" etc, it's just a different model. If you're curious to entertain this idea for a bit, I have an article you might enjoy reading: https://overreacted.io/react-as-a-ui-runtime/
> fundamentally classical OOP is message passing
OOP in its essence is about using conceptual data models as state containers. Cats, trees, input fields. Message passing is just the means by which these containers interoperate. The persistent state is the defining element.
Judged in this light, I'm arguing that the way most people write React components is more OOP than functional. The components maintain some state inherent to their nature. And I would argue that these components are still passed messages, or at least one: "render". When a component is passed the "render" message, it uses its internal state to output the appropriate DOM structure. A news widget maintains the list of headlines to display. An expanding menu holds the state for which menu items have been expanded. A form holds the errors that have been triggered from input field validation. And so on.
Thus, each component is still essentially a polymorphic implementation of an interface with a "render" method: an "instance" of a "class" that encapsulates state, even if the actual language-level class doesn't exist anymore. The mechanics are essentially the same. Since "render" was the only method being invoked on the component class, you took that lone method and promoted it to a standalone function that is invoked directly. And then you took the internal state of that class, and moved it behind the hooks API. That way, the "render" method can still access the class's internal state despite the absence of the "this" keyword. That's what I meant by misdirection. The class has just become virtualized.
Obviously it's possible for people to write React code in a way that avoids local component state. Naively we could just do prop drilling, in which case your argument that "the data flows strictly down" would actually be true (as opposed to being commingled with the data that is being stored in local component state at various levels). Or we could use Redux components, which, when written "correctly", are essentially pure functions that accept the entire application state as an argument. Et cetera.
Maybe that's actually your philosophical vision for how people should be writing React code, in a frictionless-plane-of-Platonic-ideals sort of way. Personally, mine is pretty close to the Redux model: components are just a Pachinko machine of nested functions that emit UI, and my application state lives outside of that machine, and is fed to it as an input.
But that's not how most people actually structure their React code. Instead, they continue to model their application as an aggregation of conceptual objects a la OOP. Forms. Widgets. Color pickers and the like. Each with its own persisted state. The fact that you've architected React to avoid semantic classes is orthogonal to the fact that people are still using it to implement conceptual classes.
Can you elaborate on this?
> a class is a function, but that function is invoked once. Its methods can be called many times, but having a pure render() method (like in class-based React) is really a class cosplaying as a function. Functions are more honest to what React is trying to be
useState() is a function cosplaying as a class ;) and that's really my biggest hangup. It seems like react components are not quite functions nor are they classes but rather somewhere in between... And because of JS quirks and perhaps the internal architecture/mental model, functions end up being a better choice.
> in React, the same piece of state can conceptually be thought of having more than a single value (kind of like being in parallel worlds). Classes don't model that well
Maybe I'm missing something but this seems like a false dichotomy because the equivalent in the class-based model would be the render function, not the class itself. And in that sense, your set of render functions can also have multiple values given a state. Though just writing this out does make functions feel a bit simpler.
Admittedly, you've mostly convinced me! And after reading through the new documentation, going function-only feels a lot cleaner than it did when I learned react using the old docs.
React has been around for nearly a decade now.
If you "inspect" the elements with the mouseovers, you can see they statically compiled the TS definition into the HTML.
I guess it probably uses the same APIs to read the types VSCode does, given what they state the purpose of Shiki is.
I'm disappointed by the fanatical adoption of hooks, but I saw it coming and I can't say their legacy documentation didn't warn me.
I'm happy that other people seem to enjoy them without restraint, but obscuring magical details and making side effects seem like more of a big deal than they really are in programming seems like a design choice intended to infantilize engineers and shelter them from reality.
I might finally invest some time into what it looks like to create front ends independent of any of the existing frameworks that exist today, which I think is probably controversial, but I want the decisions I make to last longer than the whimsy of engineering teams who don't care that they might change their mind in 10 years.
I think having seen front-end software come and go so many times, I'd rather write some simple utility functions wrapping `Document.createElement()` and use native event handling.
Too much fluff in front-end.
I want the decisions I make to last decades, not just a few years. I don't think that's a sentiment appreciated by most, though.
Oh right, you meant React wise... yeah...
I didn’t want to adopt functional, but let everyone make their own decisions on what to use, and now I’m very happy I did.
Asking for an API interface to be stable for decades __across all future versions of the library__ doesn't sound realistic to me. Nothing is forcing you to update to the future new version of react - which has not been released, and is likely years away - that removes support for class components.
It seems they are getting bored.
It's a joy to work in, feels "frameworky" but it's just web standards with <100 lines of convenience JS wrapped around it. There is no magic beyond what the browser provides - I like it that way.
https://github.com/retrohacker/template/
It's "open source" as a reference. Just using it for myself. There aren't many docs beyond notes to myself. But the actual framework is a 90LoC JavaScript file that is an easy read.
You're welcome to kick the tires. If you like it I'd entertain PRs and stuff but it's such a small library forking is probably entirely reasonable to make your own flavor too.
The general idea is you extend the Template base class and call "super" with the id of the <template> that will get bound to the ShadowDOM when the class is mounted. Then you call instance.mount and pass a dom node to mount it into the DOM. For child nodes, you use `this.fragment.querySelector` to select them from the <template> you mounted. It supports garbage collection by tracking children, so when you "unmount" it recursively unmounts all child instances as well. Finally it has an event emitter implementation, so changes/actions/events bubble up the DOM while state can push down through the DOM. Keeps things clean.
I recently added state methods since I was duplicating state management everywhere. Now the base template class has a `setState` that will emit a single `change` event for all changes in the current "tick" of the browser eval loop.
Cheers and happy hacking!
Edit: Hey another Arizona hacker! Right on!
Just checked out your website. I'm also doing Software consulting in Arizona, would love to grab coffee (digital or otherwise) and compare notes.
If you are in the PHX area, checkout https://www.heatsynclabs.org/ - they do Coffee & Code every Wednesday.
But I do agree with the CLI for bootstrapping being a problem for react. It's less of the CLI itself and more of the mountains it moves under the hood to setup the app. I run a single command and get an absolutely massive project. Yes the final dist file is only KBs in size, but the entire build process that gets scaffolded is still part of my project. That single command install seems "easy" but leaves me with a huge amount of existential dread - no matter how much the tooling "paves the path" it doesn't delegate responsibility. Ultimately I'm on the hook for this application and when things don't work it's on me to understand why and fix them. After a CRA install, I'm often left feeling like I'm looking off a cliff into an abyss that is going to take a huge investment to understand.
The last time I used React was inside a big company. I felt safe using it there because there was a team responsible for "owning" react inside the company. I followed their docs to start with CRA and pull in their component library. If anything inside my CRA app went sideways I _had_ delegated responsibility to another team for that, I had a support line that I could reach out to to get help and ultimately problems at that level were their responsibility. That isn't true in my personal projects - so I don't use it there.
FWIW I actually feel the same way about infrastructure components. I am Terraform certified, have been sysadmining Linux installs for over a decade, have built a serverless platform inside FAANG, played SRE, etc. But still K8S, Linux, etc. leave me with similar feelings. When I can delegate their responsibility I'll build on top of them or participate on teams that manage them, but for my own stuff there is too much to chew for me to adopt that for a personal or client project. Even with my background, for my projects I choose platforms where I can delegate that part of the stack to a vendor. These days I'm building on Cloudflare Workers, R2, and PlanetScale for my DB (until D1 is prod ready). When I adopt a service - I pay for it at a rate that gets me support contracts. That $$$ is funding an engineering team far greater than me to build, support, maintain, and understand that chunk of the system so I don't have to.
I will never forget the visceral disgust I felt in that moment.
If you make changes, I'd appreciate a follow up GitHub comment that lets me know where you made improvements, but no obligation. It's licensed under a BSD-style license so you're free to do with it whatever you like!
I just put together a content style website using nothing but web components and used a base class to put my global styles into and just inherited my various components from it.
Was also really easy to factor out shareable styles like various page layouts using a similar technique.
That just left me with doing small targeted tweaks where I would override things with custom properties along the way as needed plus any component specific styles.
Was probably the cleanest approach I’ve had in as long as I could remember. Plus it was delivering me a perfect 100 score in Lighthouse too for what it’s worth. Would recommend.
I’ve used React and Storybook quite a bit - this was inspired by those experiences. Haven’t used Backbone in over a decade.
The entire front-end framework landscape is like this. It's all designed to appeal to the kind of "engineer" that just wants to copy paste code and have it work like magic without ever thinking about what's actually going on.
It sucks that people learn this the hard way, though, and the only way to grow beyond this for people new to the field is to eventually get burned and learn there are other ways to do all of this.
There are whole generations who don't know what session cookies are! And... that's just OK.
I really enjoy JWTs, but the idea of sending all my user data on every request hadn’t occurred to me, and still feels mildly wasteful.
You could utilize a key system to gain the same advantage as not having to use persistence as one gets using JWTs when implementing session cookies, if you wanted, too.
Let’s better spend time on important things instead of wasting time trying to make UI behave properly across a plethora of browsers, OSes and platforms.
Cross-browser compatibility was the problem that jquery was trying to solve back in 2008 or thereabouts. Not a problem that react is trying to solve in 2023.
My biggest gripe with them as a technology is that they have no way to associate a `<style>` tag in `<head>` with a ShadowDOM tree (short of cloning and injecting w/ JS).
The only way to say "this CSS goes with this <template>" is to nest the `<style>` tag in `<template>` itself or painstakingly expose `part`s on everything you want to style.
I'd really like a template selector for CSS where I can say something like `::template(#id) button` to select all buttons inside the `<template id="id">` for whatever ShadowDOM it is mounted to.
Without this, I either have to do CSS in JS, CSS in HTML, or I have to use special build magic to take my standard index.css files and inject them into my HTML or JS. All of that is kinda gross.
If you want to support your case, you either inject css or you design your web components to support css variables which pierce throgh the shadow dom.
That's why I look down upon engineers with your mindset. I won't generalize all backend engineers as you did to frontend though.
I've met many who get it but of course prefer the peacefulness of controlling the execution environment and the limited state of a server.
Btw imo backend is boring because the reasons above, frontend has a lot more going on. As a full stack engineer I'd say frontend is much much harder, and that's why you have all the tech trying to make it easier to scale and maintain. That's not to say backend doesn't have it's own difficulties. It all depends what you are making too. A website is easy, a complex app is much more difficult.
Why are you assuming I am a backend dev? In fact why do you even assume I do web dev? You do know that there are many more areas where programming is needed, right?
How I took that is you just don't want to commit to your own statements, so you speak through these "others"...
And backend isn't just web. I was using a catch-all to call out how, misinformed, ignorant, and offensive your statement was.
I didn't make anything up. I just noticed that frontend devs were being looked down upon when talking to some devs and I investigated further. It seems that most frontend devs are just what is usually called "code monkeys" who just fling shit until it works. No real understanding of math, algorithms, architecture and general good engineering principles. Which is also why usually frontend code looks like garbage. I am sure there are exceptions but for the most part frontend stuff doesn't get the best and brightest people working on it. Because it's considered boring and uninteresting. Personally I don't care. Web dev in general seems to be complete and utter trash and I work on things that are on a completely different plane of complexity (as in if things go wrong people die). It's just funny that you still cling on to the idea that frontend is some sort of holy grail of engineering and absolute complexity.
Every engineer I’ve known who’s “looked down” on frontend or was dismissive of it was not only bad at frontend and didn’t understand what it takes to be good at it, but also a jerk about the perceived depth of their own knowledge.
I think there are two approaches to software development, and a gradient of people transitioning between them:
(1) I’m responsible for this system and I either have to understand this well enough to feel comfortable being directly responsible for it or delegate responsibility through my org structure or vendor contracts.
(2) I don’t understand how any of this works, it’s just a bunch of magical incantations that get me results, and one incantation is interchangeable with another.
Your dumbdumb fake engineer isn’t a fair characterization of (2) but I do believe a lot of tools in a lot of ecosystems are optimized for engineers without the depth of knowledge necessary to operate them under the API contract.
That’s totally fine when that API contract is provided by a responsible party you can delegate responsibility to. The problem comes in when you provide these “simple” API contracts on top of extremely complex internals and hand them to a developer/user who is ultimately responsible for the entire stack, internals and all.
In some cases (2) can offset risk using a “cattle” approach where multiple live versions of the system are kept up, changes happen one at a time, and you can fail over if something goes wrong and throw away the old state. I’ve met engineers who operate K8S clusters this way. They are clearly out of their depth with kubernetes, but they can fail over to a secondary and rebootstrap a bad cluster from scratch to get back to a good state. By keeping things disposable you exclude most (all?) failure modes other than “poison pill” bugs that repeatedly put your system into a bad state.
But, for the most part, these complex open source systems that try to hide complex internals give the illusion of delegating responsibility. You might get pretty far before an incident comes knocking and it’s time to pay the toll.
I think it's more charitable to assume that the designers are designing this for themselves and are just being aware of their own fallibility and limited mental capacity (we're only human after all), not seeking to infantilize or shelter some lesser class of programmers.
I'm running two businesses with a third on the way. I have bigger problems than people reinventing stuff with no benefits compared to what we were doing 20 years ago.
I'm writing software every day, so I'm still involved in front-line work, but I'm interested in being the level of productive most people aren't.
So the bigger questions on my mind are things like, "What knowledge can I build up that doesn't become obsolete?" "What social effects drive adoption that endanger those goals?"
Those are questions that have, at least immediately, very little to do with implementation details, and are questions that help me navigate whether to ignore new technologies all together, or when I identify something new when I decide to adopt it.
A part of that is looking for cues from maintainers that say, "Hey, we care that you're shipping, and we're not going to endanger your labor spent learning our intellectual property."
This is a great idea. Nothing controversial about it.
Lots of engineers out there today who don't have a simple answer for, "So what happens when one of your dependencies no longer exists on the Internet for one reason or another?"
For this to be a problem would require it to disappear from both npm and Github simultaneously, and for none of our devs to still have it on their local machine so we can reupload it under a different name.
Like, I didn’t think about that until you wrote it, but coming up with an answer isn’t exactly hard.
I’d rather worry about things that are more likely to happen, like someone accidentally dropping the prod database tomorrow.
And when you're a prolific writer and you've experienced more than just npm and GitHub, you're going to run across a scenario that makes you start thinking about how your practices in one ecosystem don't apply everywhere.
I own intellectual property composed of dependencies that can't be obtained anymore. Or two people in the world are the only individuals who are known to still have the dependency, but neither of them will supply it.
What's your plan for depending on a SaaS who goes out of business? Is everyone experiencing that on a regular basis? No, they aren't. But then you do. Once. And it changes how you do everything later, because you no longer have the privilege of not thinking about it.
I don’t think you can draw that conclusion from the fact that the comment didn’t contain all the information you expected.
I don’t think it’s reasonable to expect people to think of and mitigate every potential problem that can occur. You focus only on those that are both likely, and will have a big impact.
Will people that have experienced a vanishingly unlikely problem try to mitigate that from ever happening again? Sure. But I’m not sure if it’s actually rational to do so, when they have bigger and more likely problems to worry about.
Can I rephrase it to something more realistic?
"So, what happens when one of your dependencies is no longer maintained, for one reason or another?"
or
"So, what happens when one of your dependencies conflicts with a newer version of another one of your dependencies, which you are keen to update for one reason or another?"
I’m by no means in the web-development-sucks-lets-kill-the-build camp, I think modern web frameworks and tooling can be incredibly useful particularly at scale (people, codebase, features), but JS dependency management is pretty nightmarish rn.
It's a low likelihood, but I'd rather stay up to date when I can.
The writing has been on the wall for 4 years that hooks are the future. You _can still_ use class components. Function component with hooks is the simplest API you can get to React itself—classes are more of an abstraction.
> I'm disappointed by the fanatical adoption of hooks, but I saw it coming and I can't say their legacy documentation didn't warn me.
We all adopted hooks because it makes things easier to reason about. If you’re still having trouble understanding them, I’d urge you to dig deeper into how they and React works.
> I might finally invest some time into what it looks like to create front ends independent of any of the existing frameworks that exist today, which I think is probably controversial, but I want the decisions I make to last longer than the whimsy of engineering teams who don't care that they might change their mind in 10 years.
I can’t think of a better way to develop an appreciation of UI frameworks that to go without.
> I want the decisions I make to last decades, not just a few years. I don't think that's a sentiment appreciated by most, though.
Barely any software runs untouched for decades (documents don’t count). So it’s not that the sentiment isn’t appreciated, I think most of us would agree—it’s that it’s an impractical expectation.
Plenty of software runs untouched for decades. A lot of it powers your interactions with people on HN right now, from drivers, to font rasterization and backing layer UI composition. There are bugs in codebases that have taken 20 years for people to even notice. It happens.
When people don't have to worry about trivial details, they can focus greater efforts.
Railing against it is railing against the best-in-class example of what you want... even if it still falls short of what you want. A decade is nothing to sneeze at.
We've had React 1 (components) and now React 2 (hooks), they just managed the transition better than angular and so gobbled that market share.
jQuery stayed mostly stable, there was only one version that I can remember where it was a bit of a hassle to upgrade it. But it's quite a while ago now, so could be mis-remembering.
I think it still would have happened, but it's a shortsighted read into competitive analysis to not understand how this happened.
There was many versions of jQuery. I remember the days of hacking Drupal to load multiple versions because it shipped with an older version, but we needed a newer one for other dependencies.
React has more staying power than almost any other JS library I’ve seen. And they’ve maintained backwards compatibility on par with Windows.
The other day dang was mentioning how HN struggling under the popularity of the GPT4 thread[0]. But even then, surely HN has seen some code updates. If you look at HN as a product (and not a codebase), the unofficial/official algolia search engine is an addition.
What runs untouched for decades?
Typically written by folks who aren't even aware of the problems they are trying to solve, no tests, and event and timer leaks.
Winamp? Check.
Spacemonger? Check.
FLAC encoder? MP3 encoder? Notepad++? Savihost? TheGodfather? Flash Player? MS XML Notepad? ConEmu? Some really old build of CockroachDB? CPU-Z? Doom Builder 2? HexChat? Hell, even mIRC? Check check check check check checkity check.
None of those have been updated in years, many untouched (but not unused) since the day I installed them. This is why the web is so effing stupid. (And a hat tip towards Microsoft for keeping promises, unlike web developers)
And in many cases they STILL work better than the crappy little my-first-webapps that everyone uses instead nowadays.
Software is and should be eternal. It's these damn platforms that nobody wants to hold still with.
They could have stopped changing React. And people would have moved on to other things.
God I wish!
Also custom hooks are vastly more readable IMHO than HOCs.
You seem to be taking it too personal. There's no need to call others infants for preferring hooks.
I'm not a frontend dev by all means, but I must say, I've worked on such projects (100LOC create utils & event handling) and it was a joy. I always wanted to recommend it to people when they complain about the state of js frameworks, but I felt I've no authority to do so, because my experience is limited (only internal projects).
There are many valid complaints to make about React hooks, but I'm not really seeing those here. And I'm not seeing evidence that you've crossed chesterton's fence with them either.
I'll criticize hooks all day, but for all their footguns, they provide a level of abstraction (and a simplicity of implementation for it) that's really hard to argue with. They let you break up reactive stateful code in maybe the most scalable way I've ever seen, and their contract with the outside world allows for some crazy optimizations on the framework's part. I think the team is onto something really special here
Of course they're also easy to misuse, and they can be really "magical" until you fully grasp them. Those are problems the ecosystem will have to grapple with (and I know the core team is aware of them). Though the "magic" at least is due more to weirdness and inversion of control than it is to actual complexity. Having a grasp on how they work, I feel like I wouldn't have too much trouble implementing a basic version of the same system myself, because the primitives are ultimately not very complicated
I believe hooks are really good bones for building UIs, and I think they'll last because of it, even though the surface developer experience has some warts for now
React Hooks, if anything should have just been called React Callback Queues.
As for emotionally charged words, maybe it's not appropriate in the current zeitgeist to post without some degree first of self-censorship, but it's how I feel, so I'm going to say it.
React Hooks were fanatically adopted, in my opinion. Side effects are a regular part of programming. I find it, thusly, infantilizing to hide those details.
I wish people would stop this sort of thing, but if you want to say your part and that's how you feel, say it too.
Edit: You've apparently even been downvoted for expressing this sentiment, which I hate on HN, because it's how you felt. I wish social sites wouldn't do this. No one should be able to invalidate that.
It was one of those things where the ecosystem had to be unified, because a fractured ecosystem would have died. The React team said "this is the way we're going", and people followed along not because they were fanatical, but because that's the path that was set out. We can debate whether hooks were the right call for the core team to make, but I think it's incredibly uncharitable to say every library author who went along with it was just being "fanatical"
> I find it, thusly, infantilizing to hide those details
Software development is all about hiding details. The key is picking the right details to hide and not hide. Hiding details (ideally) lets users focus on the parts that matter to them, and gives the compiler/framework/system room to optimize the rest.
In React's case, we got features like automatic batching (https://react.dev/blog/2022/03/29/react-v18#new-feature-auto...) and concurrent mode (https://react.dev/blog/2022/03/29/react-v18#what-is-concurre...) for free, without having to modify application code, because the application code was already abstracted enough that the framework could significantly change how it did things behind the scenes without changing the contract
In terms of developer experience: I consider myself to have a fairly deep understanding of the browser platform, and a fairly-complete understanding of how hooks "really" work, and I'm still glad that I have React's abstraction layer most of the time when doing real work at my job. There are escape-hatches, as there should be, and the rest of the time I'm really very happy not to have to fiddle with all the bits when I just want to render another form and implement some business logic. I don't feel the least bit infantilized.
There are things I don't love about hooks - mainly that they do things which should really be language-level features, and that causes some dissonance - but here's what I love about them:
They expose a tiny set of primitives - pretty much just useState and useEffect (useMemo, useCallback, and useRef can be implemented in terms of these!) - which plug directly into the simplest, smallest side-effect-y things we need to be able to ask the React framework to do for us. And then, because these state and side-effects primitives have their own reactivity baked in, we can compose them into larger stateful/side-effect-y abstractions which also have their own reactivity baked-in (unlike classes, unless those classes are full React components). We can build amazingly high-level, convenient abstractions on top of this amazingly minimal set of reactive primitives, which will always themselves be reactive, no matter what. And then the framework can break them back down into the primitives at runtime (in fact, it never sees anything but the primitives), and schedule and re-order and do all kinds of nifty stuff with them without breaking contract, because the contract is tiny and elegant.
In my experience that's unique and beautiful.
It's truly amazing how React team manages to present leaky abstractions as something that community should celebrate. Or maybe what's amazing is that community is gullible enough to buy that. That's exactly the fanaticism your opponent is talking about.
Batching isn't a new feature (it's also not a React-specific feature). Looks like batching became more fragile with hooks, so they had to fix it later.
But of course React team doesn't call it a bugfix. Meet a new feature: "Automated Batching". Great marketing.
It seems like you're saying that benefit of hooks is that they made it possible to solve problems caused by hooks.
Which actually rings true: I found that React folks absolutely love solving problems, and they love React precisely because it provides a never ending source of solvable engineering problems.
Of course, since your engineers will always be busy with engineering problems, they will have very little time for product problems. So your product will suffer, but at least your engineers will be happy. They get to talk about so many cool things: immutability, hooks, batching, concurrency, memoization (ironically, the opposite of hiding the details). Given how expensive engineers are, it might be a fine tradeoff for some companies, though I personally would never want to work in those.
Also, after taking a quick look at Dan's post on batching, it seems like their solution is a great example of leaky abstraction. Their intention was to batch rendering, but they implemented it by batching state updates. Which means you can't expect to read the new state right after you set it. As far as I remember, both Vue and Svelte also implement batching, but they don't suffer from such counterintuitive behaviors.
So they're still not breaking backward compatibility. Looks like you don't have to refactor until you need something that's only hooks based.
Not sure controversial is the right word, in fact I think it is quite a common sentiment among developers, especially those that originally come from back-end. The only problem is finding a company that is willing to forgo the frameworks.
They do exist though. Here's a short list of companies that use vanilla JavaScript, most notably GitHub and Netflix:
Clojure and ClojureScript very much appreciate that sentiment. re-frame, a library for creating React apps with ClojureScript is rock solid, many years old, and still on version 1, meaning no breaking changes so far. 5 years old re-frame code still looks the same today.
Now that class components are essentially deprecated in the documentation, and the fact that React 18 support is still an experimental feature in Reagent, people using ClojureScript and React today are in quite some trouble, especially since React is the only JS framework I know of that is compatible with functional programming.
While reagent is able to emit function components, there is a performance penalty to this, since it employs some hacks in order to be compatible with both hooks and ratoms. Thankfully there are some projects that are attempting to bring modern React[1] and even bring the whole re-frame API alongside it.[2]
Last weekend I decided to try out helix, refx, reitit, and shadow-cljs' lazy-loaded modules and managed to make a pretty nice demo app that uses essentially the re-frame API but it's all modern React and hooks, and the router was capable of lazily loading modules containing code of pages the user wanted to navigate to.
I addressed this already: while reagent is able to emit function components, there is a performance penalty to this.[1]
> I also very much like Hiccup, and so do many of us, because code is data and data is code, and Helix has decided not to support that.
Hiccup is convenient to write, but it is a constant run-time cost and a significant storage cost given that you have to store long series of constructors to cljs.core.PersistentVector in your bundle, have the JS runtime actually construct the vector, then pass it through a Hiccup interpreter to finally produce DOM nodes and throw away the persistent vector, only to repeat this entire process again on re-render.[2]
> Helix has decided not to support that.
That is simply not true. From the Helix documentation: "If you want to use libraries like sablono, hicada or even hx hiccup parser, you can easily add that by creating a custom macro"[2]. These are all Hiccup interpreters you can readily use. IME there is very little difference between using the $ macro in Helix and writing Hiccup. I do not really miss Hiccup when I use Helix, and you still have data as code, the data is in a macro but that macro itself returns data...as code! ;)
While this is from an unrelated project, there are benchmarks[3] done against Reagent that demonstrate the sheer overhead it has. In practice it is not a big problem if you rarely trigger a re-render, but otherwise it is a non-trivial cost, and if you want to use modern React features (like Suspense), there is a lot of r/as-element mingling going on, converting cases, etc. that simply make Reagent feel more tedious to use than Helix.
Also, the newer UIx2, which largely borrows from Helix, is "3.2x faster than Reagent" according to one of the contributors.[4]
I think it'd be worthwhile to benchmark all of these libraries against each other and record the data in one place. Maybe I'll get around to doing it this weekend :)
---
[1] https://github.com/reagent-project/reagent/blob/master/doc/R...
[2] https://github.com/lilactown/helix/blob/master/docs/faq.md#w...
> Why do you need this?
> Why can't I just update the ref however I want!
Look up forwardRef:
> Why can't I just access ref as a prop?
Further, everyone is trying to cram all logic into hooks and components. It's to the point of insanity. Look, React is so popular I know most are using it for throw away marketing sites and other low-tier shit (sorry y'all but you know it) but.. This architecture doesn't fly in large apps; ee b2b etc.
React Router v6 seems to have no non-hook based APIs that aren't marked private!? They screwed the pooch on useNavigation; it causes unnecessary re-renders. Surprise surprise, but if they can't get it right...
Whoa, you lost me here. React Router is no shining beacon of good API design. They've never done things well, just "well enough". I'm not saying RR is terrible, I'm not saying the RR devs should feel bad about how bad they are or something, I'm just saying that these guys are not the guys you want to point at for "if they can't get it right...".
(Example of RR being not-so-amazing: Initially (v1? v2?) they required you to use attributes for the components that would render in a route, with no way to specify props short of making a custom component that wrapped the one you wanted. Then they added the ability to pass a function as an attribute, which let you specify props, so that was usable but clunky. Then, in yet a third version of the API (I think this is by v4?), we got the ability to use JSX in children to fully specify the components, which obviously was the right choice all along, it's literally the point of JSX. And again, I'm not saying I'm a better software engineer than those guys; I think I'd have got this one right but I'm sure I'd have screwed up a dozen other things.)
* It is the de facto "official" community router solution
* It's made by the same team as Remix..
With the WebComponents movement though, we are getting ever closer to being able to rely on native browser functionality for a good share of what frameworks set out to do. We're not all the way to bliss without frameworks, but for what it's worth here is my 481-byte library to support template interpolation with event binding in order to make WebComponents pretty workable mostly as-is: https://github.com/dchester/yhtml
Do it. Don't be afraid to, either.
Over the years, I've put together a list of some big and VERY big companies whose web developers hand-code their web sites without assistance from the framework-du-jour.
I recently learned about a new one for my list: A nine-figure household name tech company.
The list is useful for when I'm in bars or developer meetups or coffee shops and someone who's only been putting together web sites for a few years starts preaching about how whatever shiny new thing they just learned about in junior college is the one and only way to do things.
New to frontend?
There are things that you complain when you are junior, but start to understand better and realize why people do certain things in a certain way when you get more experience and can look at things from a higher viewpoint.
You can’t even just keep using class based as they have no way to consume hooks.
Unit testing of hook based code is non existent IME. It’s some kinda funky E2E feeling test.
React was my favorite framework before. It’s really a shame.
I’m guessing you’re referring to the fact that you can’t access component state like you can with class components. If so, that’s not required to do a unit test. That’s a sign that implementation details are leaking into your tests.
A tremendous improvement though, and it must have been a lot of work coordinating, especially the easier to digest images sprinkled throughout.
At last we can officially move past the era of class components.
Besides some explorations for suspense and server components, there doesn’t seem to be any straightforward solution to that problem unfortunately
I'm glad they fixed the janky scrolling though that has been cracking me up for a while. It's an example of a common complaint/pitfall with react and even the official docs were plagued by it. As a heavy react user and light react hater I love to see that shit lol.
Is basically useless when scrolling. All I see is a gray background, and when I stop the content disappears. Makes it hard to quickly skim to the correct section.
On mobile it just blanks when scrolling.
For what it's worth, the site has been developed by different people over time, so the choice to use Tailwind was made by someone else early on. The team working on the site now doesn't feel strongly about it either way — it's sometimes annoying but overall using it feels really nice! And I'd probably say the same about other CSS solutions too.
I think we'll keep it for now. Where it really shines IMO is fast prototyping. But yeah, it's cool.
Edit: Ok ok tbh I do like it.
It's taking a long time because there are edge cases to figure out if we don't want the upgrade to be painful for certain sites, notably the ones using anchor links or i18n. It's not far from the end so hopefully mdx2 will be merged soon.
Old but still relevant.
From the style of writing, to the style of site, I can tell a lot of effort was put into this. Will check out react this weekend - it's been a couple of years (we've been using svelte)
It’s been a game changer for me. I can’t see myself ever going back.
https://github.com/reactjs/reactjs.org (currently in the "beta" directory)
I think a good example of where React sits is Deno. The devs who are working on Deno don't seem to have much interest in React, but they are pushing a React framework, Fresh, to make it popular with regular devs. They see the popularity of React and not those who are frustrated with it. One thing is how much typical React code relies on a build step and a bunch of Provider objects. CSS Variables can help make components customizable without having to do CSS in JS.
I think Web Components, maybe with Lit or Svelte, are making more sense for beginning devs. With those you don't have to worry that you might need to work on non-react code sometime.
Beginner devs want to get a job, so they should probably spend their time learning the framework that dominates the ecosystem. Lit and Svelte are cool, but I don't think they're a great target for a first time web developer. Svelte maybe. But definitely not Lit - it's a relatively new library and a moving target without a lot of adoption, meaning there is a sparse ecosystem to fall back on, and you'll need to fill in a lot of gaps yourself (both in terms of libraries for common functionality, and docs/stackoverflow answers for telling you how to do things). Experienced devs can read the source and official docs to figure it out, but newbies need more hand holding.
Yeah, that's the point I'm making about Deno.
If Deno's own devs avoid anything similar to React hooks except when they're trying to appeal to the beginning devs, perhaps it would be smart for beginning devs to try to do as the senior devs do, not as they say?
The ecosystem of Lit is the web, which has a lot of great stuff like MDN. It lets you simply use what you learn there. No redirection, like React's onChange translating into the input event.
I agree with this, and I mostly empathize with the purity aesthetic that comes with it. But I think in practice, you need to do a lot of work for common operations that might have entire libraries dedicated to them in React. If you're a pro developer obsessed with purism, you probably wouldn't use those libraries anyway. But newbies don't have time to worry about re-inventing the wheel (or at least, we shouldn't encourage them to do that, since it will probably be a pretty shitty wheel).
All that said, I absolutely love Deno, and I think we should encourage new developers to use it, especially since it sidesteps the need for build steps in many cases.
Really?
https://fresh.deno.dev/docs/getting-started/adding-interacti...
Deno is its' own beast, that I really appreciate. And Fresh isn't really a displacement of React, it's their spin on something like Next.js, and in some ways a lot nicer even. Still using JSX.
Web Components are pretty neat, and I see a lot of things moving towards that direction... I also find Lit, Svelte and others interesting. All of that said, popularity doesn't always mean best, or align with it. Timing, interest and "good enough" account for a lot. I happen to like the React way of doing things with function based components. I know a lot of the transition logic is PFM'd away, but it's fine for most.
I've also been following Rust, Yew and Tauri... doesn't mean I'm ready to completely jump ship. React has definite advantages when working on larger projects, the ecosystem and popularity are only parts of that. I think React + Redux + MUI are a really great combination for building applications against. In the end, it really depends on what you are making, what you are familiar with and what "feels right." I absolutely hate(d) angular 1 and 2+ with a passion... I just don't like the feel of it. React + Redux is much closer to how I would build simulation logic for training/learning well before React existed. And MUI is frankly a great component library.
I still keep an eye on what's out there and what's up and coming.
> "The library for web and native user interfaces"
_A_ library, it's _a_ library not _the_ library.
600! It's hard for the competition to keep up with that.
Many people also use the react-error-boundary npm package, and some frameworks have their own error boundary APIs (eg: https://beta.nextjs.org/docs/routing/error-handling).
This is latest stable Firefox on a high spec Windows 10 laptop.
Not a great look given what React is supposed to do!