Kudos to React maintainers for their focus on documentation. A year ago I decided to learn React and the official docs were instrumental in getting me up and running. For context, last time I'd done any serious coding was when I still had to care about IE6 compatibility, so all the modern SPA paradigms had to be picked up from scratch. I spent the afternoon reading the docs and was able to jump right into coding a basic proof-of-concept app that same day. The clarity of writing, the progression of topics, it all hang together very well. Looking forward to the latest iteration here!
Its like 2 weeks since I had to grasp react, fluentui, react hook forms, webpack and a little more when I started using modules and even more when I used typescript.
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> {}
Just FYI - I would not use CRA. Even the react maintainers basically recommend you don't use it, since it's not really up to date with best principles, and they might eventually change it so that it becomes a wizard that lets you choose between other more modern choices like nextjs or vite.
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.
Back when I did an Angular project, throughout the day Webpack would fill up my RAM so hard that my M1 Macbook (with 8GB of RAM, connected to a 4K display) would eventually crawl to a halt and crash.
No such issues with Vite. It feels super light weight, snappy as hell and can run forever.
> 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.
Why are so many comments about class components, I thought we ended that debate 3 years ago?
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!
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.
> 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.
I never said they weren't weird to reason about. Side effects are hard to reason about. I was responding to the claim that hooks "push the boundaries of the language". Closures work exactly the same as they always do, but they're also something a lot of people struggle with and always have. This is all entirely normal JavaScript, but that doesn't mean it's going to be easy for people. Class components were incredibly easy to introduce subtle bugs in, especially because of the abilities to do things like define your own non-idempotent constructor or doing things like assigning to and reading from instance properties (refs are like this too, but I've seen much jankier code around class instances than the--already considerable amount--I've seen with refs).
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.
> Closures work exactly the same as they always do, but they're also something a lot of people struggle with and always have.
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.
> 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.
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; // true
> React function components are not constructor functions though, so I don't see how those are relevant
I'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.
> 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.
I would typically save the result of calling debounce (once), then call that, rather than calling debounce over and over on the same inline function (hooks style).
My debounce implementation would *not* be trying to catter to some API that looks 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.
Yes, that's true in this case, but it's not unusual to have something which uniquely identifies a debounced/memoized/cached function though, so that subsequent calls look it up against a registry and avoid repeated computation. In the context of requests, that's usually the URL itself (or some combination of the URL and some parameters). This sort of registry is basically exactly what a component instance serves as is in the context of hooks.
I guess it’s because whilst hooks improve on some aspects, they are quite counter intuitive and very easy to use incorrectly. I don’t think hooks is the final answer to side effects and state in react.
I remember a while ago, if you were to mention HOC wrapper hell in React community, most folks would jump to defend it.
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!
I have a hook that I copy around between projects called `useViewport()`. All it does is watch the current viewport size and updates its value when that changes. Because it's a hook, I can call this function from any component body:
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:
Hooks let you colocate logic that, in class components, would need to be split across multiple lifecycle methods. In practice you need to remove the event listener when the component unmounts. You can get reasonably close to a hooksy API for doing this here:
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.
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
}
}
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)
This is a hook version of the same code as near as I could guess it.
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
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.
> 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.
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?
> 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.
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 });
I imagine most of them could be, but this is an API which would require a lot of additions to the existing React.Component API, and would end up duplicating a bunch of already-existing functionality for something which in the end would probably need to conform to the same rules hooks do, but with what certainly feel to me like clunkier ergonomics. You'd also still be left with the problems of class components like poor minifiability and unreliability for hot module replacement.
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].
I was trying to discuss the merits or demerits of hooks overall - just wanted to point out that a class based design doesn't need to be significantly less powerful or HoC-level awkward to use.
(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)
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.
I made no such assumption. I've just never seen it discussed, where as, for example, I've seen "mixins" discussed and dismissed (justifiably) as an alt.
> How do this two behaviours compose together/interact with each other?
I think the disconnect is reusability. You should not use inheritance class-based react components. Therefore, if you wanted this logic in another component, you either need to copy/paste it or use an HOC. Other commenters have shown why HOCs are a pain, so I won't give into it there.
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
I believe context predate hooks. You are right about error boundaries, probably should be revisited.
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!
This is actually a perfect example for why classes are insufficient.
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.
Sure! It's generally about "stateful logic". Basically logic that relies on state and/or the lifecycle methods (componentDidMount, componentDidUpdate, etc) as with classes those are only available as part of a component.
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.
I appreciate the effort, but it's a bit over my head with respect to react stuff.
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.
With merge props I mean passing down the local state to the next component. If you see the react article I linked it’s the injectedProps.
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.
A React component is supposed to be a pure function, a mapping from props+context as input into an output which is a React element plus sideeffects (which when executed by the React library will often change the context). State lives in the context.
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.
> 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.
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.
You are not wrong, I think renderprops are valid, and I still use them in some situations.
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.
Take a look at Use hooks libraries for examples of code that you may want to have portability with (this is they typescript one because its the one I've used)
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.
Hooks are much worse because they poorly emulate classes.
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.
Most importantly: hooks make functions that look pure actually impure.
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!
Exactly! I could not put it better. All the talk about side-effects. Hooks make side effects more confusing, not less, for the reasons you perfectly explained.
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.
All true. I work on a massive React codebase and it is a challenge to follow the logic because at any given time there could be a dozen effects firing off from a single state change. Granted, having the dependency array helps follow what's firing off, but when effects kick off other effects it gets into a mess. (Probably a sign that we should refactor some things) This problem isn't solved by class-based components either; years ago when I first started working on the codebase I found class-based components with gigantic componentDidUpdate() full of hard to follow conditionals and setting state. That said, hooks makes it easier to refactor problems like this in my experience.
> Most importantly: hooks make functions that look pure actually impure.
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.
So I spent some more time reading through using HOCs in React and I think I'll have to give functional components more of a try. The specific example I was thinking of is https://github.com/graphql/graphiql/blob/50674292c55eadf0e61... but this component will probably always be complex because of the Codemirror interaction.
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.
React functional components are not supposed to be complex. It's easier to split FCs up than in Vue for example. Even React's class based components discourage splitting components by adding more boilerplate.
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.
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.
Skimmed over your article. The infinite loop situation is not exclusive to hooks and very much exists in classes.
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.
This kind of reminded me of the module pattern [1] popular with ye olde jQuery plugins. Just realized that hooks try to emulate what JS does natively, which is keeping track of all local variables in a functions calling scope. Meaning that if that function returns an object, that Object still has access to all the variables declared when that function run.
It’s funny that controllers are conceptually not that different from hooks just with classes. Question: can controllers access everything on the host? As that would easily turn into a giant mess. Probably you shouldn’t but isn’t that the same as hooks rules?
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.
Classes with state are a nightmare. You can never tell what has changed or where. Having the state in the hook system where it's visible to your tools and you can actually see what's changed between one render and the next is a huge win.
After using Vue for a long time, React felt like an unforgiving piece of cr*p. As time went on and Vue was getting its Composition API while kind of moving in the overall direction of React, I decided to try class-based components and didn’t like them that much (this was a couple of years ago). Recently, I went through these new docs and read and applied the stuff I learned, and I can say it was a surprisingly nice experience. I just needed to get friendly with a composition-ish, functional-ish and JSX-ish way of thinking. Alone the thought of HTML-in-JS instead of JS-in-HTML made me crazy back then (it still kinda does). But when you get the hang of building actual user interfaces instead of just websites, it makes a lot of sense. If you’re comfortable with javascript while daring to take the less-opinionated route as opposed to an all-out framework, you'll simply feel the power!
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!
As someone who worked with both, vue 3 is miles ahead imho. Scoped css, well working two-ways data binding (now being mimicked again with signals after mobx demise) and reactivity, easier to get performance out of the box without the hook madness.
In many ways I do agree with you, but by now all these front-end frameworks and libraries, including their server-side rendered counterparts (like Nuxt, Next, etc.) just keep borrowing things from each other.
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...
Oh, nice for reminding me, SSR is also better imho on Vue with Nuxt than anything in react world. Next.js has the tendency of monkey patching plenty of APIs, even broke native fetch when uploading anything bigger than 14 kbs.
Remix is the gold standard of SSR to be honest. I worked with both, Nuxt and Next.js, but Remix with is heavy usage of web standards just makes the most sense to me.
To clarify: there's no "native" fetch to patch. Both Nuxt and Next have been adding it to the runtime to normalize it across Node.js versions and environments, and to add critical capabilities that the Web standard lacked (like being able to read the `set-cookie` header or customize `Host`).
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]
> 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.
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.
I never really got why people LOVE two-way binding to the point that they add things like mobx to projects. IMHO the one way data flow makes things so much easier to reason about.
MobX is fantastic! But you do have to change your philosophy quite a bit compared to what you would do in vanilla React - namely you need to displace most of your state management to the outside of the components.
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.
Two-way data binding trades a slightly more complex and less transparent rendering model for more efficient rendering. You don't re-render the entire component and diff the two components you have to understand changes, you simply change the leaf.
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).
I think typescript support was a priority aspect to be addressed for Vue 3 making it sort of a first-class citizen. The whole new composition aspect of it was to address large codebases and large-scale applications. So I understood that V3 makes all the difference if TS support and working on larger project is important for you. In fact, I think if you are coming from React and try it out, you are highly likely to feel right at home there.
Interesting, I had an opposite experience. I started out using React when using class based components was a way to go then stopped for quite some time. Years ago I had to use React for something and decided to use the new approach with hooks and it was such a painful experience - it was harder to learn and use, also I introduced many ridiculous rendering bugs. I still feel that component based approach was somewhat easier and more straightforward to get into even if more verbose.
Yes, it can be tricky, and sometimes I too catch myself asking, "why is it designed like it is" with regards to hooks, I won't lie. But, there is something there that makes it attractive to me where it was off-putting before. Alone as a complement to more extensive state management libraries, or other tools intended for larger scale apps, it serves as a nice middle-ground solution. You can build your own bits of logic in quite an accessible way now too. In other words, for me personally I feel that there is something distinct and interesting going on here.
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.
I also like what it inspired in Vue. At first I thought "oh no they're taking what React did and throwing it into Vue". But it actually ended up to be a very true-to-Vue variant with just some idiom similarities. The new Vue 3 ecosystem with Pinia blends together nicely
Is it possible to add Svelte gradually for simple and small parts of a website without a compile step? It surely seems amazing and all the rave these days!
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...
I recently went through the entire beta React docs (took me about 3 weeks) and was blown away by the quality of the tutorials. The live code playgrounds and challenges are amazing. With the recent debate about reactivity and which framework implements it best (solid vs. react vs. vue vs. svelte), I felt that what mattered most to me as a junior dev is the quality of the documentation. I gained a strong enough understanding of the complicated concepts (dependency arrays and the useEffect hook) because the docs were so good. There is always room for improvement in JS land and other frameworks are constantly innovating, but the thing I always fallback to is: how good is the documentation?
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.
Are class components really on the down & out? I tried going all in on hooks with a complicated app using lots of web workers and generally heavy computation and complex logic (a game). It turned in to a bit of a mess until I went for a refactor into classes and everything became much more clear.
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.
One pattern you might like to try in this kind of code is writing your heavy imperative logic in classes (not React class components! just normal classes), and then using Hooks very lightly to "connect" and "disconnect" things.
One example of this pattern is Searchkit [0] which performs most of its logic inside a singleton Searchkit class which is instantiated and passed as a prop to the root React component. A bonus is that it's easier to implement bindings for Angular, Svelte, etc. since they can rely mostly on the class and just need to call the appropriate methods exposed on its interface. For example, it looks like Searchkit now suggests using InstantSearch (react-instantsearch-dom) [1] from Algolia, i.e. an entirely different maintainer, and it creates the bindings with a `Client(new SearchKit(...))` adapter [2] around the class (see the code on the home page at [0]).
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.
Oh wow didn't expect to hear form you! Yes that's what I'm in the middle of right now actually to enable online multiplayer: everything regarding the game is in a (class-based) state machine in pure JS that can run frontend or backend and be interacted with via a websocket client (this actually helps with a11y too, navigating a board game via aria-labels was a pain in the butt), and the UI (still WIP) will be far thinner. Perhaps I'll give hooks a try again for that.
All of the “tanstack” components seems to be going this route which I’m really excited about. I tend to really like that stack and roll most other things myself.
Should make it easier to switch frameworks in the future if the need arises.
For what it's worth, we do think there is also a lot of value in building a deep vertical integration in tooling so that it can take advantage of React's unique features. A little bit about this here: https://react.dev/learn/start-a-new-react-project#bleeding-e...
I wont dispute the benefits to vertical integration, but I still think there is benefit into keeping things modular as well. I've been working on an application for the past five years that's stayed afloat partially because keeping things small and not becoming over reliant on any one library.
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.
Because you have to write more code. If all you have is a single variable that gets updated with no complex logic then writing a reducer seems excessively boilerplatey.
I joined my current company around the time hooks reached peak hype, and whilst the team I joined were refactoring away from class components. My previous gig had used classes. My current codebase is complex (a video player) but pushed all the core logic out to Rx and custom code, rather than going deep with hooks and react ecosystem. So I’m still way more familiar with classes despite ostensibly working in a hook-y codebase.
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).
I like them too but they tend to promote shittier code. The codebases I inherit that use classes = clean, easy to follow. Ones all in with functions and React hooks = trash.
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 have had a similar experience, and feel the same way.
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.
Most React codebases benefit from the simplicity of functional components, because they fit into the model most people have of websites + web apps. If you’re doing anything complex, you’re in a very small group.
I would argue the inverse: if your project relatively simple enough that hooks don't feel messy, then it probably doesn't need to be a React app to begin with.
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.
The most frustrating part of this is when you run into some library that is all in on hooks and insists you use hooks, but your project has been using class-based components so far. I was having a great time making a game prototype with react-three-fiber until some model loading code was just like "fuck you, use hooks" and I ended up wrapping a bunch of functional components in special ways to basically isolate the hooks stuff so I don't have to deal with the headache of it all gradually encroaching on the rest of my perfectly functioning project. It's frustrating because every time I wonder how to make it not a mess, the answer is "haha, we lied, they're barely supported; class-based components are for losers and you should feel bad."
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?
On a thread a while back there was a Microsoft eng who was on a team that looked into trends with bugs.
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).
Yes, yes they are. Next to every 3rd party library is written to only work with hooks. Every tutorial is written using hooks. The new docs (finally) but them front and centre.
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.
I think generally you should start with functional components, try to keep things simple, and use class components as the exception and not the rule. My own app is mostly functional components, but I did fall back on a class component in a case where I needed the lifted state of a function reference to remain current across renders.
I was relatively happy with the React class model, for me it was very clear just to override any required methods in a class.
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.
When hooks came out, myself and the other senior devs on our team had that same inertia-driven reaction. We knew classes, we were comfortable with classes, and they worked. So we asked the same question - "Why hooks?"
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.
Same experience for my team a couple years back. We hated the idea of changing everything to hooks, then we actually started using them and fell in love. So much complexity fell out of our code we couldn't justify sticking with class components.
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?
To me, the main benefit of react hooks is it eliminates higher-order component hell. It's so much easier to extract common behaviour from different components to a re-useable hook, rather than wrapping a class with a million different higher-order components: `withRouter(withFoo(withBar(MyComponent)))`.
I can't figure out why anyone chose to use react...
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.
The original API when it was originally released was much saner. That was before JavaScript even had "classes" which meant React classes didn't have nearly as much boilerplate. After they migrated the API for React class components to be JavaScript classes it all went downhill. And then the functional people came and the rest is history.
For web UIs my preferred framework is SvelteKit. It's based on Svelte which is more of a language than a library but code is basically HTML, CSS and JavaScript, it's simple and fast. I really want to migrate to using something such as Yew for scripting web UIs though, I can't take JavaScript/TypeScript anymore lol.
React had a small simple API surface, made it super easy to create reusable components, and was more performant than some of the other dominant solutions (AngularJS)
It’s more of an idea than a library. The idea’s good but the implementation is awkward and incomplete. You’re endlessly writing boilerplate and incorporating libraries for even basic and typical cases that a more thoughtful design would make unnecessary. Even with multiple evolutions the problems are only somewhat resolved. The virtual DOM was a bad idea from day 1 and remains so. React wants components to be the unit of rendering, but an app wants a component to be a unit of app UI. These concerns end of conflicting and competing, which the dev has to do the heavy lifting to resolve, with things like useMemo/Callback. It uses javascript to represent HTML (HTML is already the ideal way to represent HTML declaratively).
> HTML is already the ideal way to represent HTML declaratively
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?
Declarative just means declaring the result rather than specifying how the result is achieved. (It naturally implies the existence of something else that can realize the declarations. E.g., HTML needs the browser to render it.)
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.
The reason why I don't think HTML is the ideal way to represent HTML declaratively is because HTML does not have pure functions or any sort of proper first-class values.
Is there an explanation somewhere on react.dev covering how Hooks actually work under the hood? They're the most magical part of React, and everybody I know (including me) had a hard time actually grasping how they work and why, for example, you can't use a hook inside an if branch.
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.
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.
My high level understanding of how they work is that they remember the sequence in which they were called - so if you call the same hook three times, those three calls are held in a list - and future actions that should retrieve them get access to the correct data.
If you were to call them in an "if" branch it would mess up that queue mechanism.
I read the code when they added them. It may have changed, but here it is:
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.
Basically the way of thinking about is that there is a runtime that knows what component is rendering, and your hooks are communicating with that global runtime. This is why hook order and consistency matters - there is basically something globally that identifies a hook by its index order of execution and the identity of the component instance that is currently rendering.
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.
1. They work by setting a global variable to the value of the current component then calling the render function. Whenever you call a hook you're effectivelly dispatching it to the component in question, OOP style.
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.
Here's how I'm pretty sure they work (although I haven't actually looked at the internals). Since your `<Tagname props>children</Tagname>` gets turned into `React.createElement(Tagname, props, children)`, this means your `Tagname` function isn't called directly. So before it calls that function, it sets up a "context" that the hooks will use during that function call (with a global variable pointing to that context so the hooks can find it). We could use an array with one element for each hook invocation. So each useState would use a different slot in that array, etc. This is also why the order and number of hooks must always be the same for a given function, since their identifier is their index in that array.
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.
Everytime I try to understand how something in react works I actually look into how it's implemented in preact. Hooks is around 500 LOC. https://github.com/preactjs/preact/blob/master/hooks/src/ind...
React does basically the same, just in a more complex way (because fiber and native etc). But just giving you a mind model the preact implementation is enough.
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.
This doesn't have anything to do with React, it has to do with refresh rate, vertical sync, and the fact that your cursor position is updated by hardware and doesn't undergo the same update process as the rest of UI compositing in an operating system.
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.
Hm, right. Good to be corrected. Not sure why the lag is so big, though. Looks like it's 3 frames looking at slowmo capture of the screen.
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.
Your intuition is correct - there's nothing to indicate any delay.
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.
There will always be the potential for lag because the OS UI thread is updating the native mouse pointer position separately from the browser UI thread updating the page.
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.
I know your post is sarcasm but a base M1 would perform the same as M1 Pro, Max, Ultra in this case. It's the single thread speed that matters the most in web performance.
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.
For anything like animation in React I would use CSS transitions or using direct updates on refs without triggering rerenders. It takes only a simple model of "what a rerender is" to understand that this is running potentially _way_ more code than it needs to for every update and injecting scripting frames before the actual animation update happens.
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).
If you're doing anything animation related, there is no point in using React's useState because it causes re-renders. The right approach here is to use useRef.
Do you have mouse that have much higher update rate than monitor? The mouse event frequency is generally capped at the update rate of monitor unless you use `pointerrawupdate` which will actually give you raw mouse event without delay.
Unbelievable that they’ve gone ahead launching this, going all in on hooks, while the fundamental problem with them, which was raised in 2018 is still unsolved: https://github.com/facebook/react/issues/14099
And they just handwave it away in the docs with an imaginary future API. Embarrassing.
And yet, entire startups have been created and acquired during that time, many using React and many using hooks. It appears this "fundamental problem" is far from a show-stopper. Ultimately, that's why people like React: it gets the job done. The API is small and extremely stable. The last major breaking change was hooks, and that wasn't really even a breaking change - just a new paradigm that you should move to eventually (class components do still technically work!)
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...
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)
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).
This is certainly an issue/annoyance (along with other things I don't care for about React), but calling it a "fundamental problem" and "embarrassing" ignores the vast, vast majority of React users who manage just fine.
The quick version is we're looking at this from two different sides:
- 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.
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.
>Is there something I should know about useEffectEvent vs useEvent?
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.)
Sounds like that’s probably ok in practice - you can use it nearer to the location of the useEffect.
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).
We can only play very well with the stuff that works by the React rules. It's the only direction that it makes sense go into, IMO — we are exploring a particular paradigm, and we might as well squeeze everything we can out of it. So to reap its benefits we also need to work within its model. We do have escape hatches for the other stuff but this is exactly the reason we're cautious about recommending something that is in a different direction than our model. At some point we want to rely on some property, and if it's not there, it just doesn't work.
We’ve actually been through our code after this conversation to look at the impact. Switching to the model where we use useEvent in the local component only is working fine and is easier to reason about.
The idea with them is to “break” reactivity. In the local space where you define them that’s totally fine because you’re obviously doing it in order to have useEffects that only run when their real dependencies change.
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.
I really strive to find actual example of shooting myself in foot with passing "stable" callback to component below.
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.
TBH I can't even figure out what useEffectEvent does after reading the docs and looking at just about every Google hit for it. Please let me know if you figure it out.
OT but if I never see connection handling and connection callbacks mixed in with React components and stale state refs in a real, production app I'm responsible for.. It'll be too soon.
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?
Yeah, I’ve helped build some of those huge apps, and I’ve ran in these issues and wasted countless hours debugging and working around them in all of them.
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.
Can you be a bit more specific about which issues you've ran into? That would help me know whether we're addressing them or not. (The linked issue is very broad so it's not super clear which part of it you're referring to. We think it conflates a few unrelated things.)
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.
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.
Can you elaborate on what this issue prevents you from doing? I write production react code that is used in medical devices every single day and I have never had this problem stop me from developing a particular screen or piece of functionality, and I use pretty much every niche edge web API that there is.
Hooks has made me 3-4x more productive. If a technicality is stopping you from enjoying it, that is such a shame.
What are you talking about? Hooks are fine and are out there working fine for everybody. They have some rough edges but it's not, like, some catastrophe.
This may come across as naive, but could you expand on the significance of this issue for people who don't use React every day? I see that lots of people think this way about Hooks, so I'm honestly just curious about what React users think is such a big deal
its not naive. this isn't a problem you're likely to run into or have to work around. It is a thing that pedantic people bring up to justify the trouble they have keeping up with the pace of change in front end dev.
IMO React is missing hooks to work with promises. I made that hook myself but I think that promises along with AbortSignals (to interrupt promise if React decided to cancel it) are basic JS API and React should just support them out of the box. I saw too much code which deals with async functions in useEffect in a buggy way. Things like ReactQuery are nice but they should not be required.
You probably won‘t answer, but what do you think of the (imo uncalled for) wishful thinking that react should‘ve stayed in the direction of Class components?
Missing standard downstream promise cancellation is arguably one of the bigger problems in the whole javascript ecosystem. In C# almost every async function has a CancellationToken parameter, same in Go with Context.
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.
One thing I find strange about the new documentation is how React.createElement is considered a "legacy API". Doesn't JSX transpile into calls to that function anyway?
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.
JSX has not been compiling to createElement() for a while. (If you have the modern transform enabled.)
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.
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(....
ReactDOM.render has been deprecated since React 18. If you're running React 18 with ReactDOM.render, you should already see an error urging you to switch because essentially ReactDOM.render works in React 17 mode.
Why did react (effectively) get rid of class components? Conceptually, a component seems better represented by an object/class than a procedure/function. And aren't classes just really functions under the hood anyways?
> Why did react (effectively) get rid of class components?
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.
Weren't as of a couple years ago—they're represented by an object in the React code. Not the case anymore? I'd be surprised if they'd messed with that, it seemed pretty fundamental to how it operated—hooks were implemented on top of that, for one thing.
OK, so the core of React changed and they no longer use a "functional" component to build a component object, which used the be the primary data structure in the state-machine-like engine at the heart of react, and to which they used to attach the lists that contain a component's hooks? That's all changed?
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":
>Conceptually, a component seems better represented by an object/class than a procedure/function.
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.
> 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"
I really don't think React is OOP. There is no misdirection here — it really is a different conceptual model.
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/
I did enjoy that article; I think it's a great overview of React's essential architecture. But I don't think it obviates the point I'm trying to make.
> 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.
> It really has a bunch of powerful properties one might not expect.
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.
I'm really not sure why they're so insistent on phasing them out. Have I written one recently? No. But I sure do appreciate that they exist for cases where you want fine grained control over what's happening.
I'm always wondering, why it took web devs so many years to create some meaningful reusable UI components (and I still don't see a wide adoption of something like that)? I just can't understand how reimplementing even such things as simple buttons every time from scratch is productive
Because there is a lot of creativity involved when you are placing pixels on a screen. You’d be surprised by how many different ways you can render a button. There is no single abstraction that satisfies all business requirements.
Currently microsoft product line like SharePoint, Dynamics (Power Apps), Office, Teams, etc, etc is based on FluentUI https://developer.microsoft.com/en-us/fluentui#/ components that gives also the developers a close enough solution to extend UI within said products.
VanillaJS/web standards all the way. I love being able ship something knowing exactly what’s going to end up in the browser. I really don’t get the react cargo cult fad, and when it finally dies it won’t be too soon.
I don't get how anyone can build a serious application on vanilla JS. I love vanilla JS, but it would require one to re-invent the wheel to manage all the state. You'll end up borrowing tons of ideas and rolling your own framework. At that point, you might as well have used something like React, Svelte, etc...
Looks really nice! Also, super happy that the interactive examples are not super sluggish code sandbox iframes, they're actually usable! A more typescript-first approach would have been nice, or at least typescript/JavaScript toggles, but the React team seems to be aware that that's an area they need to work on.
That is something I really dislike about the react-query docs, the examples are code sandboxes. Glad to see they didn't go that path here. I find they add a lot of friction when I am looking for a quick reference.
Well, bummer. I have a mature product using React Components which are now legacy. It looks like in the future, I'll slowly migrate these over to functional components, as is standard in the documentation.
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.
This is exactly what I'm doing with my personal "framework" I use for client contracts. It's just Web Components with a handy class based wrapper. I call it Template, since it's based off <template> tags.
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.
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.
I like how for your brief documentation you start with a single index.html file. Too many web/framework docs start with a command that gives you a directory full of who knows what, which tends to negatively affect beginners more than anyone
I can pinpoint the exact time where I fully became disillusioned with front end work and it was the day where I realized that in order to be productive with these tools, you had to set it up with the CLI.
A lot of these frameworks don't actually need a CLI to scaffold them. React was (at least originally, I haven't checked in on it in a while) designed to be _incrementally_ adopted so you can retrofit an existing website with React components.
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 just swapped my app to Vite from CRA and it's far better. CRA was pretty simple too especially if you weren't using typescript, I assume you were post "eject".
This is awesome! I was actually looking for this very kind of thing the past couple of days. I was searching HN and Github for "vanilla js" and various "framework" queries but not finding anything. Thanks!
Watch out for the Web Component gotchas, though: the shadow DOM encapsulates the CSS cascade meaning your styles will not apply to your components unless you explicitly apply them to each one. Also, events don’t automatically bubble up!
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.
Glad you like it! Feel free to take it and make it your own.
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!
> Well, bummer. I have a mature product using React Components which are now legacy. It looks like in the future, I'll slowly migrate these over to functional components, as is standard in the documentation.
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.
When you maintain your own software for periods of time that outlast front-end engineering trends or IC's entire career tracks, your opinions might change.
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.
At once I sympathize with your viewpoint and also have to say that React is the longest I've ever seen a Javascript thing stay in the notoriously fickle good graces of the frontend world – short of perhaps jQuery.
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.
It has lasted as a project. Are you saying that no library should try to improve their performance, internals or API?
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.
I’ve been around this space for over a decade. Software that’s easy to change, tends to outlive the rest.
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.
And yet, I‘ve said no to projects that would‘ve earned me a nice sum because they were started at a nice pace with a dev that intended to build his own frontend framework, and it slowly grew into an eldritch horror.
I've got a multi-gigabyte folder of portable windows programs and utilities, where parts have been untouched for 15+ years. For the most part, everything still runs.
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.
I think you’re probably referring to the lifecycle methods, which have relatively clear and unambiguous names in class components. The thing is, those names may make you feel comfortable that they do what their respective names imply, but in reality they’re an abstraction away from how React really works under the hood. Hooks, for better or worse, get you a little “closer to the metal,” so to speak.
Also custom hooks are vastly more readable IMHO than HOCs.
The docs are kinda poor on them IMHO. They don't approach the hooks from first principles. For example look up the useImperativeHandle hook docs. Use those to explain simple questions:
> 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...
> 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.)
> seems like a design choice intended to infantilize engineers and shelter them from reality
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.
Eh... having worked with a Facebook developer tools team, they quite literally do dismiss the vast majority of the engineers at the company as "those guys who wouldn't be able to understand the stuff us devtools people do".
> 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.
You seem to be taking it too personal. There's no need to call others infants for preferring hooks.
> 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.
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.
I've spent the vast majority of my career outside of front-end development. You're being extremely condescending and presumptuous so I will not engage further.
For a lot of Linux distributions, you have a problem and you go online to find a solution. Depending on how old the stackoverflow (or w/e) answer is - the answers you find are out of date. So you find yourself sifting through a pile of possible answers and you need to figure out which answer applies in this context.
Compare that to FreeBSD where it feels like every effort has been made to stay backwards compatible with the legacy APIs for decades. I found a web-book on FreeBSD network administration once and almost dismissed it because it was last updated in the 90s but, upon closer inspection, most of the book was still relevant and I could administer a FreeBSD network using the same commands the book was using!
As an industry I do think we devalue tribal knowledge and the accumulated ecosystem of documentation that gets invalidated with every breaking change. Every time you ship a breaking change all of those hard learned lessons across all of your users that are sitting in the back of their gray matter, in their blogs, and in stack overflow answers all become invalid - at best they get removed/updated and at worst they stick around and mislead future adventures.
one of the reasons why Docker is so popular [among web devs] - it's basically the same inside and all that changing parts are handled by cloud in good case or by greybeard sysadmins in worst case.
I worked with class components first. Built a whole app in them. Now we’ve migrated to functional and I look back at the class based ones wondering how I ever dealt with that.
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.
Right. I don‘t understand the HN crowd. A Haskell thread every weak, tons of people arguing that <current year> will be the breakthrough of lisp on desktop, but they scream for OO patterns in their frontend frameworks.
:) True. My first web development experiences were on AOL Hometown and reading people's 1337 scripts from dynamicdrive.com. DHTML! Updating PHP scripts over FTP! Shared hosting.
> 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.
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.
I use re-frame, but you cannot deny that class components being deprecated is going to be pure trouble for re-frame. Re-frame relies on the reagent library's Reaction interface. While re-frame does not depend on anything else in the reagent library, it is natural to use Reagent for your rendering. But reagent itself emits class components by default, since it predated hooks by several years.
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.
But Reagent supports functional components as well, with hooks and all. I don't see why I'd have to change an entire library because class components are being retired. 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.
> But Reagent supports functional components as well, with hooks and all.
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 :)
I don't think it's productive to throw out enotionally-charged words like "fanatical" and "infantalize" in these conversations.
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
I think it's sufficient to say that having been exposed to hundreds of thousands of lines of code across different industries and programming languages, I don't like React Hooks, and I don't even like them repurposing the word "hook" which is otherwise known as a trampoline in other programming language contexts.
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.
> React Hooks were fanatically adopted, in my opinion
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 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 React's case, we got features like automatic batching
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.
Agreed that investing in standards is always a good bet. But at the same time, we have so many web frameworks in part because what is spec'd in plain JavaScript/HTML/CSS is not quite high-level enough to be really be a productive foundation just on its own. Going all the way back to raw `Document.createElement` will come with its own special pain.
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
I'm slowly trying to get most of my projects and client ones to leverage more and more web components, I'm sick of reimplementing the same things over and over.
I'm usually the guy who is several versions behind on every library and I stick with what I know instead of exploring new features. I will say that React has been pretty pleasant to work with in regards to not forcing me to rewrite my code much. They're pretty good about keeping the old methods around our several major versions so you have plenty of time to make changes to your app or just keep doing things the old way.
I won't immediately update, since future feature development will take priority, but eventually I will want to update since the current industrial risk is that surrounding dependencies may have bugs that are a risk to customers.
It's a low likelihood, but I'd rather stay up to date when I can.
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
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.
> front ends independent of any of the existing frameworks that exist today, which I think is probably controversial
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:
Hooks were the beginning of the end of my interest in React. Truly a terrible concept. Way to magic. Not overly aesthetic, not to mention the async programming techniques the js community has developed for over a decade all kinda break down with hooks.
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.
> Unit testing of hook based code is non existent IME. It’s some kinda funky E2E feeling test.
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.
They are just so simple to me that the "magic" never comes into play. Just a function that gets called when something in an array is different between renders.
> seems like a design choice intended to infantilize engineers and shelter them from reality.
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.
I don’t think this is a truly irreconcilable stance and I don’t think it’s exclusive to frontend, you run into it up and down the stack.
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.
No. It's not too complex. It's just boring. And you people keep reinventing the wheel and presenting old concepts as "revolutionary" or "innovative". That is why you get looked down upon.
Or you don't understand the needs or the technology because of your stubbornness and ignorance.
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.
I didn't say I look down upon frontend devs. I said that because of the way frontend seems to be chasing trends a lot of people look down on you. Maybe I should have said "That is why you get looked down upon by a lot of people".
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?
The reason you think frontend is more complicated is because of historically terrible terrible decisions made. Frontend is made complex because of the initial idiots developing it.
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.
The only people who look down on frontend or backend engineers are people who don’t understand what it means to be great in either domain, and are too arrogant to take the time to learn and appreciate its nuances.
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.
It's just front-end engineering in general, you don't get this with people who work in other subfields of software engineering. I get it, though. It's OK until you need to learn and grow beyond it, which I think anyone doing this long enough will.
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.
Generally a session cookie requires that you send over a unique ID. That's usually it. If you use JWTs, you're sending over the same amount of data if not more, but now you have to do things yourself instead of just utilizing features browsers natively have.
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.
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.
That's how it is intended to work to avoid that using two web components on two different websites leads to major inconsistencies because the root shares several classes.
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.
This is exactly the “just use react” stereotype. Download a random react component from NPM and 90% chance they just added wrapper divs until they could get the CSS to (kind of) work.
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.
> 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.
Sounds reasonable and like good code and design, perhaps we "engineers" think hard enough already. I just started reading A Philosophy of Software Design and it talks about "deep" modules (they give the example of UNIX's open creat close lseek and read vs java's I/O interfaces).
good to know, it’s been a while since I’ve used it (moved on to htmx for my uses mostly) and a quick look on the homepage still espouses use of setState and class components.
Your will be surprised how nicely a functional approach with hooks can improve your frontend architecture. Side effects are the source of all evil, it‘s quite useful to take special care.
I think the thing is that you're focused on this specific implementation detail of React. I just don't care. Not that you're not right, either! It's just a matter of competing interests.
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."
You probably should have a better understanding of how hooks work and why they are better before posting a long and meaningless rant like this.
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.
> `Document.createElement()` and use native event handling.
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).
But have you built whole applications in them? React is one of the few frameworks where I almost never feel like they didn’t consider consider all use cases.
funniest thing to me is how many people identify themselves as "react person" or "angular person" and I am thinking "you'd be better off saying you are "cobol person"
> 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
This is a great idea. Nothing controversial about it.
You end up having to answer to people in interview processes that have fewer years of experience than you, and have written sometimes orders of magnitude less code than you, and wonder why you make "weird" decisions not knowing that those decisions have been made with a collection of exceptional experiences.
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?"
> 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 with Docker containers? And proprietary binaries? My point being, as you've proven in your reply, that most developers are going to only think of the most obvious cases.
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.
> My point being, as you've proven in your reply, that most developers are going to only think of the most obvious cases.
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.
> So what happens when one of your dependencies no longer exists on the Internet for one reason or another?
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.
As someone who learned web dev with Svelte, it seems so obvious to me that React is full of convoluted apis born from tech debt, and such a bad DX compared to what is possible today. I imagine if I learned React first, it wouldn’t be nearly as obvious to me how mush worse it is than it should be.
I've never understood why React components are considered to be pure. The output is not just a function of the props, it's also a function of the state (as in `useState`) and context (as in `useContext`).
Finally! Although looks like Google still has to update so newcomers are likely still going to head to the legacy docs and not realize it (and the legacy page still links to beta.reactjs.org vs react.dev).
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.
We'll make the legacy site binner a bit more prominent and clear -- thanks for feedback. I planned to but got distracted with other things during deployment.
Perfectly understandable, it's a huge change and these smaller things fly under the radar easily. Congrats to you and the team for pulling off the major transition smoothly!
I‘d love to have a simple way of calling ReactDOM.renderToString() on my express server that does block to actually wait for API calls so that SSR‘ed HTML is populated. I am very willing to trade performance against simplicity here.
Besides some explorations for suspense and server components, there doesn’t seem to be any straightforward solution to that problem unfortunately
I've been using I think the beta version of this recently, it's definitely an improvement and makes the react api seem a lot more coherent than the old version.
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.
Please don't hesitate to report things like janky scrolling to the issue tracker (https://github.com/reactjs/reactjs.org/issues). We were changing things a lot and I'm sure a bunch of regressions could've crept in, so all reports are super helpful.
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 desktop it works "fine", problem is that the scroll gets caught inside every code window on the way down. So scroll the page a bit, cursor above an example, scroll the example until end, then page keep on scrolling, then stuck on a new example etc.
Looks like some good resources and a neat redesign. The domain move makes me realise how odd it was that before they had 'org', which is normally used by charities/non-profits. I'm guessing Typescript is the force behind dropping 'js' from out of the domain too.
As the person who most often pushed for .org, I can say it was because I wanted to emphasize the community nature. React isn’t a commercial offering. (I had to talk the team out of react.com as the main brand for this launch; although that URL works, I think react.dev is more reflective than .com of what we’re trying to do.)
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.
Docusaurus is a fine project, but we thought it is important to use React very directly — so that we have a good sense of what it feels like to make an app with React.
Awesome. So now that this has rolled out, can you all help the Docusaurus team figure out how to migrate from MDX v1 to MDX v2, and why it's been so difficult? It's been six months of work and it's to a point where we're considering moving off a React-powered docs solution altogether so we can unblock the rest of our app from getting off React 17.
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.
Credit to the React team. The docs prior to this were poor - I believe the team admitted this.
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)
Hmm, I can't find the "Edit this page" link that many Docusarus (which I assume this is?) sites often have, and I can't find the repo either, so I can't submit a PR, but... There's no RSS feed for the blog :(
That's exciting news. As mentioned below, it'd be great to have some implementation steps for companies and projects who aren't at the Next.js framework technical level. There must be some middle-ground!
How a team who isn't Next.js or Shopify can implement in their own frameworks, without needing to dig into Next.js sourcecode. The example app released around the announcement is quite old and bare-bones, and I'm sure there have been learnings and other guidelines since then. In short, it would be great to have a little more than a spec and an undocumented webpack plugin to work from.
Tell me, if React is so great, why isn't Draft.js blowing ProseMirror out of the water? And why aren't difficult parts of VSCode written with it?
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.
Draft.js is now archived, and not being maintained. That can be for a number of reasons. When React first came out, it was several years before it really started to pick up steam. I think the Angular shift from v1 to v2 helped out React a lot, along with create-react-app scripts.
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.
> I think Web Components, maybe with Lit or Svelte, are making more sense for beginning dev
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.
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.
> The ecosystem of Lit is the web, which has a lot of great stuff like MDN
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.
I like classes because they're easier to code review and easier to read/reason about. The artifical limitations are good. At a large org that's way more important than initial write.
You can pretty much skip React and go straight to Qwik as it solves N+1 and it fixes the ability for low quality devs ruining perf with useEffect since you have signals ala Solid
It’s funny how react.com redirects to reactjs.org and that redirects to react.dev domain because most companies only use newer tlds because dotcoms are unavailable
All of the new documentation is horribly laggy in Firefox and all of the code sections are broken (eg. the visible cursor position does not match the actual cursor position...)
This is latest stable Firefox on a high spec Windows 10 laptop.
Not a great look given what React is supposed to do!