Although, when I see code snippets on the internet I always wonder how do people test this.
The fact that dependencies are declared as imports and all the logic is inside the component function, it feels like you have to shuffle things around just to make the thing runnable outside of React.
There are special tools to "render" react components in node-based test runners.
Of course, you need to write your components in such a way that they are easy to isolate. You can also make use of composition and maybe even mock out components.
Opinion: Although you can test hooks in isolation, I find they tend to be fairly primitive and testing them in combination with components can be more useful.
In isolation, unless you have very complex hooks, it’s a bit like you’re testing react or JavaScript themselves. When testing a component which uses the hook, I find you get to test the behaviour and expectations a little better - it’s close to a real use case.
I think even those examples border on usecases for more complex state management along the lines of redux, Zustand etc. To me, useState with a complex object is an antipattern. I'd much rather use multiple useState invocations. This eliminates the need for extendState and for handling functions and promises.
Also, take a look at Redux Toolkit Query and React Query. Both provide advanced query state management based on hooks.
> To me, useState with a complex object is an antipattern. I'd much rather use multiple useState invocations.
The issue with that is that sometimes you need to make transactional changes to your state (i.e. either update 3 variables together, or not update them).
That gives you 2 options:
1. bundle the state up in a complex single object with useState
2. extend the space state of your app to allow for mid-transaction rendering (i.e. only 2 of the vars have updated and the 3rd has not).
#2 feels a lot harder to me, tbh (although I do strongly dislike #1. and would like an alternative.)
That scenario to me is a sign of a greater workflow than what should be in the view. Some other model should be handling that transactionality, not the view itself.
Before React 18, it was possible to get multiple renders depending on where your calls to set state occurred. Inside different event loops, async calls, etc would lead to multiple renders even if they all settled within a few ms of each other and before a render occurred. The batching algorithm wasn’t aware of each asynchronous context.
In React 18 I believe this is handled via batching of state updates. If you update various pieces of state within a component before it has the chance to render, it will update all changes at once and render once.
There are exceptions I’ve forgotten but generally it solves this problem well.
To me, the core problem with using two hooks is that there's no signal to other developers that the two pieces of state must be updated atomically. Sequential setState calls might be batched, but there's nothing preventing someone from only calling one of them.
I'm not sure I understand. Which two hooks would be called in this case? In the original example I'm seeing where there's anything atomic involved across multiple hooks.
Are you referring to the usePromise and useAsyncExtendedState? If so, it's okay to use those hooks independently. They work fine without each other, but they also work well together.
I’m saying that if you have multiple pieces of state that must be updated atomically, it makes sense to store them in an object in one useState hook because it signals to other developers that they should be updated together. The fact that React allows you to batch the renders if you store the state in two separate hooks is unimportant.
I actually prefer the opposite, just one state per component. And I use immer for that (use-immer, specifically). Most of my components have one set of props (passed down from the parent), one set of state, execute one GraphQL request.
The extendState one confused me a bit as I just keep one “atom” of state per useState since Hooks were introduced (so you’d end up with foo, setFoo, bar, setBar). I hadn’t really considered doing it the way they are doing it (which feels more like the old this.setState API). How do other people use it?
I agree. Though it's possible that it makes sense to have some ideas "unified" into a single state variable if only some configurations of the pair of values are sensible, and you want to keep both in lockstep - or if things trigger off of state changes in any of several values, and you want to modify several values at a time.
There are other comments here making the same general point, that extendState seems like a bit of a weird, and unnecessary, use case, and at first reading the blog post I agreed.
However, it "clicked" with me when I saw how they use their usePromise hook (to me it seems like their useAsyncExtendedState hook is only really useful in conjunction with their usePromise hook). That is, it is extremely common in React to call some remote API, then update the state when the Promise resolves. You also have a common set of things you want to handle: showing that the request is still pending, handling an error, updating a part of the state with some subset of the response object when the Promise resolves, etc.
It's very possible to do this in React without these custom hooks, but you'll usually find you have top-level state variables in your component that mirror the success/error/pending states. You need to do that all over the place.
Using these custom hooks makes it easy and consistent to make these remote calls but handling the stuff about the remote Promise request is essentially "contained" by the result of usePromise. Seems like a really nice, clean way to get consistency across remote calls in all your components.
I use hooks the way you do for independent information, but when the states are related (like a userID and a userName for example), then it's better to have them as properties in the same object. If they tend to both change at the same time, that could cause a double re-render, as well as temporarily having an inconsistent state when one has updated but the other hasn't yet.
Yeah. It depends on the data structure and how you'll use/update the state. `extendState` exists for when the situation calls for it. I avoid abusing it.
For deeply nested data, I would probably use `immutability-helper`. Many deep spread operators can get ugly and confusing pretty quickly, in my experience.
Normally syntactic sugar is fine and has a near-zero cost, but in this case I don't think that's actually true.
Given the way hooks are structured, if I want to use both `setState` and `extendState` in a `useCallback`, I'd need to pass both to the dependency array, which is just extra effort (which defeats the purpose of the syntactic sugar imo).
I think I use it like you do. From my perspective, it's just much clearer when I haven't seen the code in a while and need to immediately understand what's going on. Also, I'd be interested in profiling the two different ways of managing state because I can't imagine that recreating a composite state object could be as fast as updating one value, but I haven't done that and I haven't really looked into how the spread operator works under the hood... maybe it's super fast.
For medium-sized state — for example a single-level object — I usually use useReducer, with the "reduce" function a simple destructure merge. The reducer looks like this:
It ends up looking a lot like the "setExtendedState" function in the article.
Often I'll throw in a few helper functions in a useMemo, too, and then provide the whole thing through Context. It's like a mini-Redux for some section of the app.
It's unnecessary. Just use individual useStates for each property. Makes it easier to type, too. Otherwise you might as well go with something like Redux.
Completely agree, also individual named state hooks are great for semantic reasons, they will make your code more readable and more easily maintainable.
the whole point of setState is that it forces you to think about component state from a centralized point of view and help avoid modeling it the wrong way (eg a bunch of optionals instead of an union type for the whole state). If you split them to a bunch of states then state becomes just mutable var in a different syntax.
That's not really true. Regardless how many setters you have or invoke, state only changes between renders. Of course state is mutable, that's the whole point. It's not mutable during the render function though. It's also easier to accidentally mutate an object assigned to a const than a string or number.
Also, the setters are usually passed around all over the place, so no, I don't think the point is centralizing component state at all.
centralizing as in when you setState({ a: true }), you also think "wait a minute is having a `state.b` still makes sense, now that "a" is `true`", maybe it should be setState({ a: true, b: null })
> It's not mutable during the render function
I don't think that's the point at all when people talk about mutability. To me mutability is just a proxy for "lacking access control", which again goes back to the point about centralizing state updates. So arguing about the technicality of what is mutable and what is not is pointless.
that doesn't matter. Look at frameworks in which you have states that when mutated, trigger a rerender, such as svelte. Suddenly (scattered) setState calls are not very different than (scattered) assignment statements
I too felt like that for a long time. Eventually I got when and why Redux is useful, even with the boilerplate. Also, modern semantics and utilities greatly help. Redux Toolkit, RTK Query, reselect and so on.
Actions make state more debuggable. Selectors make rendering more performant and often help structure complex rendering logic. Also, Redux is a way of not having to define asynchronous control flow by composing components, which can get really messy.
I like Redux because it's portable—you can easily rip it out of a React project and use it anywhere—and because it provides a really nice way to separate backend-talking-stuff from frontend-rendering-stuff, if you're dividing up dev tasks. Nice clean place to split off a library for whatever service(s) you're consuming.
And with Typescript, it's not a bit unpleasant to work with.
Redux Toolkit doesn't require much boilerplate anymore (IMHO). I found Redux to be absolutely appalling and never used it (despite having some familiarity with functional programming and the ideas underlying Redux) before RTK came along.
I don't know what applications they are developing but an application only needing two custom two hooks, would be a quite trivial one. Not to mention the hook in the article extendState is basically bringing back the Component `this.setState` semantics but not much else.
> In the case of modern front end engineering and React especially, you can reduce everything down to two simple concepts...
> Rendering the current state
> Updating the state
Another day, another React team coming to the belated realization that hooks are an inelegant solution to problems that have already long been solved. Separate your view layer from your application state. Get all those network calls the hell out of there. Relegate your components to simple stream transformers -- props in, HTML out. If you're doing anything other than that, your function components aren't really pure functions.
It's frustrating to watch an entire swath of the industry continually rediscover its own inadequacies year after year...
> Separate your view layer from your application state.
It is actually separated, not from React though because React will need the state (data) at some point anyway. We'll go into more detail on that in the next post. To touch on it briefly here, shared application state exists on its own at the top level of the app, as a composition of stateful hooks. See here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216...
What I mean is that you're violating standard separation of concerns when you have components that themselves are capable of dispatching network calls and updating the state in an async way directly within the components. As a result, those components no longer have an instantaneous view of the universe, and that makes them harder to test, harder to reason about as isolated units of abstraction, and so on.
> Every possible API request also exists on its own outside of anything React
That's good, but I would take it once step further and disallow any React component from directly invoking those methods.
Of course you're always going to need to do something asynchronously when the user clicks a button or what have you. But in my opinion, it's a lot more maintainable to have that just be an event that the component fires, and then have something else listening for that event out-of-band (and then sending the network request / updating the state to say "connecting" or "request failed" and so on). What happens in async land is not really a pure component's business.
(I know I'm definitely out of lock step with the current React ethos on this, so permit a crusty neckbeard his pet gripes.)
I understand. If you get a chance, please clone the core API and app (https://github.com/Molecule-dev) and play around with it. Maybe you'll change your mind?
The React ecosystem has spent the last few years trying a model where the application layer separated from the view layer with a pure functional state management solution called Redux. The overwhelming response? People didn't like it.
Decoupling systems is a trade-off. Pull your network requests out of your components and you have two bits of code that are easier to test. Indirectly, the component is still going to call that code, and it's up to you to manage the complexity of that indirection.
Not every application needs separation of concerns, and in many, colocation of concerns reduces the cognitive burden, because you can reason about components in isolation. To me, that's a more powerful guarantee than a function being strictly pure.
idk if “the overwhelming response” is people dont like Redux. some people are very vocal about their dislike, yes. But 1/3 of survey respondents use Redux despite other choices existing. theres a reason it won the Flux wars.
Maybe overwhelming was too strong, but I don't see many people getting excited about building new projects with Redux any more. Presumably some proportion of that 1/3 are stuck on Redux, unwillingly?
I like (and use) Redux on a daily basis, and I don't think it's a sensible choice for lots of React apps. Maybe the overwhelming rejection that I've witnessed is people discovering that they used it for stuff they shouldn't have.
It won the Flux wars, but there are state management approaches based on paradigms other than flux. There's at least the model that Recoil/Jotai uses (atoms?), the model that MobX/Valtio uses (mutable observable state), the model based on state machines (XState), the model heavily using React context (Constate), etc.
Sorry, but I have stopped using "developers don't like this" as a measure of the technical quality of anything. A lot of developers like things that are pleasurable to them in the small, but harmful to the codebase in the aggregate, especially over years of maintenance cycles.
I'm writing this stuff to try and push back against the whims of that majority, because I think they're ill-founded. Just because a majority of people believe something to be good and true, doesn't automatically make it so. It still has to stand up to empirical evaluation on its own merits.
I'm not sure I agree with this. One of the elements of a well-designed system (whether we're talking about software libraries or anything else) is that the designer should reward people for doing what they like. If you design something where the correct approach cuts against people's natural inclinations, they'll just use something else. The correct answer is to design paradigms that make the right thing pleasurable.
I agree that systems should be rewarding to the user. But there are rewards and there are rewards. There are short-term dopamine fix rewards, and then there are the rewards that you can only really appreciate after having invested some time and energy first. It's like fast food vs a lifetime of diet and exercise.
The churn in the frontend ecosystem reminds me a lot of fad dieting: people have never really experienced working in a paradigm that enables them to stay healthy through regular diet and exercise over the long term, so they turn to the latest shiny gizmo hoping that this time it will be different.
I agree with you that there's a balance to be had here (although I will note that there's a pretty big difference in physiological effect between tens of thousands of years of evolutionary history and a few years of writing code). That being said, I don't think we should make "rewarding to the user" a second-class citizen to "technical quality" - I think it's an important component of good technical quality that something is pleasurable to use. My impression is that a lot of self-serious systems designers don't embrace this view because it's easier to blame the technical incompetence of their users.
This is true, and especially true for tutorial and course authors. There are a lot of low quality tutorials that are essentially a dev talking about how they prefer to wrute code rather than teaching any useful, generic material that applies universally. Often what works in their courses for a lone junior dev would not scale to a small team, and following their ideas would produce a pretty terrible app.
I agree with your point that not every application needs this degree of separation of concerns. But the problem, in my experience, is that individual developers are not very good at knowing where that line is. And the line can move over time as the application grows.
For any project that I'm responsible for, I don't feel comfortable designing around a paradigm unless there are some guard rails to prevent developers on my team from accidentally tying the code in knots. How enjoyable is this for the developers? Are they sprouting angel wings and playing the lyre as they write code in iambic pentameter? Probably not, no. They have less freedom of motion than if they were left to their own devices.
This is a larger debate: where to fall on the spectrum between unadulterated developer bliss and having an application that is still maintainable in 5 years. I don't put much stock in developer bliss, but I do appreciate that the sword cuts both ways.
Hi, I'm a Redux maintainer. I've written extensively about the fact that A) Redux _has_ been overused, B) that many of the complaints were really more about the standard code patterns needed and the "boilerplate" involved, and that C) "modern Redux" with our official Redux Toolkit package and the React-Redux hooks API has solved those "boilerplate" concerns.
Redux is still by far the most widely used state management tool with React apps (my estimates are around 45-50% of React apps use Redux), and we try to give clear guidance in our docs on when it does and doesn't make sense to use Redux.
FWIW, we get highly positive feedback on a daily basis from users who tell us how much they love using Redux Toolkit.
I'll add to that: I love redux and most of the complaints or concerns that I've heard about using redux (from a few teams) have been misunderstandings of how to integrate redux into an existing application or an application design. I think this has mostly come from junior-level folks (independent of title, there are a lot of non-junior devs who can't integrate new patterns into their "senior" level understanding of code, but I digress) and folks who don't work on the front end much (or ever). I was one of those folks until I started a side-project that used redux and I had to implement it in a greenfield project and had the liberty to refactor as I progressed and my mental-model "updated" to integrate the new framework.
My complaints with redux were that people couldn't leave it alone. At least in my experience, there was a lot of middleware or libraries built on top of what was a conceptually very simple library.
Using the redux-toolkit and the hooks, with none of the additional junk is a great experience.
This was exactly my reticence... back when I was learning various approaches and trying to come up with a first-principles derivation of which way to go, I came to the conclusion that I wanted to avoid immer like the plague. So discovering that RTK requires it has made it difficult to consider embracing.
I'm very curious, what concerns do you have about Immer? fwiw I just wrote a sibling comment that goes into some detail on what problems Immer solves and why we included it in RTK:
Still worth it though, imho - if you have a chance do give it a try. It makes Redux much less verbose and is an ideal abstraction for state management. It never bothered me that much, but forking Redux Toolkit and removing Immer is still an option if you feel strongly about it (should probably be easy).
Yeah, concerns about Immer have come up a number of times. The usual concerns people have are:
- Too much "magic" as you said
- Proxy-wrapped data values can be harder to inspect in the browser's debugger
- Seeing "mutating" code may be confusing
I understand those, but from my perspective the benefits of Immer far outweigh any potential negatives.
Ultimately all Immer does is track attempted mutations to nested data, and replay them to create the immutable updates as if you'd done all the nested spread operators yourself. There were dozens of immutable update libs in the ecosystem already (I had listed most of them in my Redux addons catalog), and accidental mutations have always been the number 1 cause of bugs in Redux apps. So, a better way to write immutable updates was clearly valuable.
Immer drastically simplifies all that update logic:
- Removes all the extra object spreads and slices and concats you would have had to write previously, so the code is much shorter and easier to read. It's also more clear what the actual intent of the state update is.
- It also completely eliminates accidental mutations _in_ reducers, and also _out_ of reducers by freezing the state.
So, shorter code, easier to read, and eliminates the #1 cause of Redux bugs.
The tradeoffs are a couple KB of bundle size (which is quickly paid for by having shorter reducers), and needing to understand that Immer is there and doing this "magic" for you.
The one still slightly nagging annoyance is that inspecting Proxy-wrapped data in a debugger is painful, but Immer does supply a `current()` util to extract the plain JS data if you need to, and we document doing that:
Thank you for the detailed response! My experience seems to be different, as in my opinion immer solves a problem that should not arise in nice code (in most cases) - state should not be that deep anyway. It also takes away a bit of the simplicity of Redux which is its greatest strength, imho.
But as I said, I still highly recommend Redux Toolkit. After dealing with the code using MobX and the complexities brought by its reactions, I would take Redux anytime. Immer is just a small unfortunate detail, but I can live with it.
> Kudos on Redux Toolkit! Anyone who complains about boilerplate with Redux has obviously not tried the Toolkit version.
You still have to understand Redux's weird boiler plate concepts - actions, action creator functions, reducers, thunks, ... - to understand and use Redux toolkit. Redux is broken from the bottom up.
Acemarke isn't just "a Redux maintainer", he's incredibly active on Reddit, HN, and Discord, helping people understand Redux better, and also, when it doesn't make sense to use it.
I can't speak to his code contributions, but in terms of documentation, tutorials, and community engagement, most open source projects would be lucky to have similarly prolific contributors.
FWIW I _have_ written actual code in the libraries too :) I wrote React-Redux v7 by myself, shepherded the design of our hooks API in 7.1, and did all the coding work for v8. Also created Redux Toolkit, added a couple additional APIs, and a bunch of other stuff :)
That figure is "Weekly Downloads", not in total. None of them had updates in less than two months. Also, React has published 10x more versions than Redux. So if anything, Redux might be more popular than GP assumed.
My estimates are primarily based on NPM download stats for React, Redux, and React-Redux, as well as more anecdotal evidence like "what libs are you using?" polls that I see on Twitter and such.
These are all obviously _very_ imperfect proxies for actual usage stats, but they're all that I see available to work with.
FYI my last attempt to estimate "React state management market share" was in a Reddit thread earlier this year, with download numbers and caveats included:
The biggest thing I miss in react ecosystem is decent redux ORM. https://vuex-orm.org is just so great for so many use cases (agree that it might an antipattern in many situations). Is there any chance that https://github.com/redux-orm/redux-orm, which was actually what inspired vuex-orm, would get more love from anyone to become an actively maintained library?
Great question. I actually was one of the earliest users of Redux-ORM, back when I myself was first learning Redux in 2015-16, and I wrote a blog tutorial series in 2017 that showed how to use it.
At the time, the main benefits to using Redux-ORM were easier immutable updates for items in the store, keeping items in a normalized state structure, and managing relationships between items for both lookups and updates.
Since then, Redux-ORM has changed maintainers and had its API tweaked. Meanwhile, we added a `createEntityAdapter` API [0] to RTK, which handles normalization and some CRUD-style updates to the data. Even without those CRUD helpers, you can still do `state.entities[itemId].someValue = 123` thanks to Immer, so immutable updates are taken care of.
At this point, the one remaining thing that Redux-ORM really handles that RTK doesn't is that specifically relational behavior - ie, given a `Post`, look up all its `Comment`s by an array of IDs or something. You can do a decent amount of that with selectors, but yeah, Redux-ORM's API here is nicer.
We haven't tried to provide any of that ourselves because this becomes a much more complicated and app-specific topic. But, I'm always open to suggestions for APIs and use cases. If you have ideas for things that we should consider adding to RTK, please do file an issue so we can discuss details!
That wasn't supposed to read as a criticism of Redux. Redux was just popular enough to be the medium where people discovered that you pay a price for the indirection you need to keep view and state separate.
It won't ever be a one-size-fits-all solution and unfortunately, Redux will have to live with the legacy of plenty of users finding that out the hard way.
That being said, I think Redux Toolkit is a great improvement to the vanilla experience and I'm always impressed by how much I see you out and about on the internet weighing in on these kinds of threads. Inspiring stuff!
A big challenge with the React ecosystem is that it's the first technology many new developers work with. They don't have a frame of reference for what alternative approaches exist, and therefore 'best practices' are taken on faith and followed blindly until their nuances can be learned through experience.
That's not a bad thing (and it's better than the alternative of not caring for design patterns whatsoever). It's part of the learning process, and since there are so many beginner developers who are using React, their perspective is much 'louder' than in other dev ecosystems.
You can see this in the absurd amount of introductory-level React content posted to Medium, DevTo, Twitter, etc. This has bred a very strong 'follow-the-leader' culture where, when the one person is the room who _does_ know what they're talking about makes a statement, others will repeat it verbatim without understanding its nuances due to a lack of experience/context.
Redux suffered heavily from this. New React devs in 2017 were faced with mountains of tutorials which all used Redux. Many of these tutorials were written by other newbies. Your mental model of React dev was then shaped around Redux. Therefore you would put everything you could into the Redux store, which is a bad idea -- you usually don't need form state in there, for example.
Then some React thought-leaders saw this problem and inadvertently created a counter-movement by raising how Redux was overkill for some use cases, which was misconstrued as 'you shouldn't use redux _at all_'. The pendulum has been swinging back and forth ever since.
Yes, not every application needs separation of concerns. But some definitely do. It's not black and white, and that unfortunately means there's no definable 'best practice' that can be tweeted or blogged about and followed blindly -- it just has to be learned from experience.
I would go one step further and say that the counter-movement is the visible effect of newcomers discovering that separation of concerns was a bad decision for the simple apps they were building.
Or maybe more accurately, discovering that it's often simpler to separate by concern at the component level, than at the app level.
We all ride the pendulum until we find the shade of grey which works best for us.
> Therefore you would put everything you could into the Redux store, which is a bad idea -- you usually don't need form state in there, for example.
Why's that? IME it is usually simplest to just put everything in redux. For example, if you have a form under some kind of tab navigation thing, you'd ideally want the form state to be preserved even when they tab out and then back in. Putting it in redux means you don't really have to think about it
But this could also just as easily be solved by moving the form state up, or at least the preservation of each tabs state one level up, without using redux at all.
You'd still preserve on tab change just as easily, while using just reacts mental model.
Like, I don't want _everyone_ to use Redux. I just want people to understand "here are the strengths, weaknesses, and use cases of tools X, Y, and Z, and when it may make sense to use them".
Honestly, we Redux maintainers (Dan, myself, Lenz Weber) have had to spend far more time telling people when _not_ to use Redux than we have trying to advertise the library :)
When I rewrote the Redux docs tutorials last year, I made it a point to put "When should I use Redux?" right up front on the first page:
I've been reading "blue" DDD book (by Eric Evans) and "red" book (by Vaugh Vernon) and that was a completely "my whole life was a lie" type of experience and relief at the same time. It's just so great to have the principles of who to structure the code. It, by definition makes, your codebase structure meaningful. Because it's structured according to some common knowledge, not your random thoughts at the time you were writing code.
I was surprised to find so little DDD react sample codebases. Let's say for backend there is huge amount of samples, i.e. https://github.com/kgrzybek/modular-monolith-with-ddd . For react/frontend I have bookmarked only https://github.com/talyssonoc/react-redux-ddd/tree/master/sr... and few more, but those others does not meet the optional criteria i like really much - at the highest (or at app) level all codebase need to have folders app, domain, infra and ui. Simple rule, but simplifies life a lot.
So my question is - is DDD for some reasons not very applicable for app frontend development. Or it just never became popular. Or maybe DDD is popular amongst react developers, just I am not aware of this.
I think you've missed the point if that's your takeaway. The article's solutions actually double down on hooks, proving that they are powerful and expressive enough to manage complex and asynchronous state precisely because they don't couple view logic and application state. That's why a generalized hook like `usePromise` is possible in the first place. The fact that you can trivially drop in a hook to make a component start dealing with asynchronous state validates the hook paradigm for UI development.
Remember: components using hooks are pure functions! That's what makes them so effective.
They are _not_ pure functions. A pure function will return the same value for the same arguments every time. This is not true of functional components using hooks as the data received from calling the hook can change over time due to side effects.
> A pure function will return the same value for the same arguments every time.
Yes, and given the same outputs provided by props + hooks, components will return the same value. That's why you can write hooks functionally, and why we can do away with class-based functions entirely. With the exception of refs, there is no actual internal state that your component needs to deal with.
The necessity(!) to wrap each and every basic JavaScript API (useEffect v. setTimeout, useLayoutEffect v. queueMicrotask, useInterval and usePromise v. using Intervals and Promises) does not at all showcase the "expressive power" of React.
It was fun while it lasted but it's time to let go. React's become JQuery
How would you use setTimeout to replace useEffect?
useEffect works by running on each render (no dependencies) or running when rendering and dependencies changed. Where does a timeout factor in?
useLayoutEffect also doesn't work based on the microtask queue - it uses React's internal scheduler, which I would imagine makes sense in the context of the library. Replicating it with queueMicrotask is probably not a trivial task.
That's like saying "ugh Python's become JQuery because XXX function is not part of the standard library." You seem to be upset that people are building more useful primitives using the hooks paradigm?
React deliberately ships with only a few hooks, more or less the fundamental ones that you cannot yourself implement. They left the rest up to the community to explore, because it is very difficult to predict how a new paradigm will be used. Hooks isn't just useState and useEffect, hooks are a way of writing reactive logic.
It is very impressive that the community is able to produce new tools that simplify annoying problems like managing asynchronous state with just a single function, in a way that can be more-or-less dropped into any existing component.
The ReactJS community is not simplifying annoying problems. The JavaScript community did that. The ReactJS community is quite literally re(!)-implementing promises here to make them work with a purported library(!).
This is insane. You shouldn't need to download useConsole from npm.
> The ReactJS community is quite literally re(!)-implementing promises here to make them work with a purported library(!).
What? I don't think you understand hooks very well. You can already use promises with hooks by creating them and then calling `setState` however you want in the callbacks to deal with pending/success/error. It's such a common pattern that the community has developed a `usePromise` hook that abstracts away all that logic for you, meaning that you can treat any promise as a stateless value to be consumed by your components, one that will automatically manage its state for you and guarantee your UI is consistent without any additional effort. I never have to deal with managing asynchronous state ever again! That's what a good library does: allow you to build reusable components that abstract away complexity. Hooks let you abstract away state management, the bane of UI development.
I can tell you from experience dating back to Backbone and Marionette that it has never been so easy to deal with async state before. Coupled with Typescript, I also get the guarantee that I've accounted for every possible case (success, pending, error). It's a zero-cost abstraction.
I understand modern JavaScript development is a giant leap forwards compared to JavaScript development 10 years ago. Nobody is arguing with that.
Your last statement is completely wrong, though.
ReactJS/hooks are not zero-cost abstractions. They impose a cost both in terms of real performance as well as cognitive complexity.
And when you run into performance issues you're almost certainly confronted with this pattern of escalation (assuming you've done nothing wrong):
1. Litter your Code with calls to useMemo/useCallback.
2. Re-evaluate your business needs because you've exhausted the hooks "primitive".
3. Iteratively and painstakingly force more and more logic out of ReactJS' scope until you've met your benchmark targets.
Following along quickly requires intricate knowledge of poorly documented but overwhelmingly complex ReactJS internals alongside(!) whatever knowledge of JavaScript internals is required.
You have to have and process 2 complex, unique and outright byzantine models in your mind simultaneously.
Compare this to Angular(!) which lets you use standard web APIs, additionally exposes useful standard(!) primitives, and(!) provides you with straight-forward(!) opt-out mechanisms.
Yet here we are, quite literally downloading useInterval from npm 40,000 times a week.
I think this should lead you to re-evaluate your assumptions about hooks, because there is a reason why React has blown Angular out of the water. The reactive hooks model of UI development is successful because it allows you to write almost any component as a stateless function with well-defined side-effects. Yes, it requires a different way of writing them, but it's worth it for complex applications.
The fact that hooks like usePromise and useInterval can be trivially written on top of standard web APIs and guarantee that your component hierarchy will update consistently without dealing with internal state is a groundbreaking achievement. I think you are focusing too much on the fact that hooks like usePromise exist, instead of on what they enable.
Have you ever used usePromise, and seen how it turns a stateful object into a stateless one? It has never been so easy to encapsulate asynchronous state.
> ...there is a reason why React has blown Angular out of the water.
- The ensuing paragraph suggests you are conflating Angular with AngularJS. Angular is build on top of proper, standard FRP and stream processing primitives. It is "reactive".
- Angular is in widespread use in the enterprise world.
- The reason ReactJS is much more prevalent everywhere else is it has no barrier of entry. None at all. This, however, is irrelevant with regards to the issue I am raising. I am complaining about the complexity ReactJS dumps on working professionals.
- I am not advertising Angular. I am telling you that EVEN Angular manages to suck less.
> The fact that hooks like usePromise and useInterval can be trivially written on top of standard web APIs ...
your preference for model/view/purely presentational components would benefit from using hooks in your model layer as opposed to a class component with the old lifecycle hooks. hooks are the model
Unless you mock a hook at the module import level (which is hacky), you lose the easy testability of your component. That's long been bothering me about hooks, although I use them plenty.
At the end of the day, components will always need a way to interface with state. Often times you want local component state because the cost of abstracting that tiny bit of state into some top level application state is disproportionately high - it just isn't a pragmatic choice. And for application state, ultimately if you have any interactivity in your application (which, if you have state, presumably you do), at _some point_ you're going to have to do something other than "map props to HTML".
> If you're doing anything other than that, your function components aren't really pure functions.
Nobody claims they are pure functions. They aren't - but that doesn't really matter. Its an API choice that encourages productivity while still having tools for the abstraction that you are advocating for.
State hooks can be seen as a sibling of props - both are inputs to the component, but whereas props "push" data into the component, hooks "pull" data from somewhere else. This _is_ separating concerns - when a component needs data that isn't inherently something the calling parent owns, then making it a prop would just be forcing that component's concerns up the tree. Often it is better for it to just be an impl detail.
I don't find the article here super compelling, and as a general practice it is often better to author your own custom hooks that encapsulate things like the underlying HTTP request away so that your component is only dealing with its own concerns, but functionally there's no difference and its just a matter of how you split up your code.
In my experience, the cost of writing and also maintaining code is proportional to not necessarily the (typically fixed) cost of the boilerplate, but the "distance" between the various pieces needed for things to work.
Let's say I need a single bit of state in my component. If I store that in a local `useState(false)` and update it with `setState(newValue)`, it is extremely easy to write, and also follow what is happening and how it is happening.
But if dogmatically say that this is poor encapsulation - this state should live in some Redux store, with actions and action creators, reducer functions, etc. there is inherent complexity, and I think in the case of a single bit used in a single place that it is pretty easy to see that the complexity is disproportionate to the actual needs at hand.
But the way I see it is a sliding scale - your state starts to become more complex? or maybe how the state is updated requires some additional business logic? Maybe you want to start using that state in different places? Any of these reasons can be cause to introduce abstraction, but my preference is to introduce the smallest abstraction that achieves the benefit you're after.
Sometimes this abstraction is hoisting state up - now your component doesn't manage its own state, but rather it communicates with its parent via props+callbacks. Now its a little harder to follow (need to consider this component, its parent, and how the prop/callback are being used), but for that complexity we now get to share that state with sibling components.
Sometimes this abstraction is moving it to a reusable hook. Now you have a similar scenario where to understand how things are working you have to understand the component and the hook, but for that cost we can now share business logic.
Sometimes that abstraction is moving it into a redux store, with all the boilerplate that goes with it. For the right use case this is not a bad thing, but I would only want to do this if I'm getting a proportionate amount of value for the increased cost.
I have been coding in react full time for the last 8 months in large codebases. I have to say that initially hooks were hard to understand especially useEffect but then as I got comfortable with them they were great to use and I was really productive. But then, as I started shipping production code with react I found that hooks are extremely difficult to debug and very complex to wrap your head around when debugging because of side effects and implicit behavior.
I now really dislike hooks because of the amount of implicit behavior and useEffect poisons the codebase to add even more implicit behaviors/side effects to your application.
They make the rendering behavior really difficult to reason about. I caught a bug where you had to click a button twice and the reason why it happens is extremely difficult to reason about: https://stackoverflow.com/questions/58106099/react-onclick-n...
In my opinion what we need is an explicit state machine of the rendering of a component like vue’s component lifecycle. And these hooks we are using for state management we should instead use xstate to make the state transitions of a component explicit. React hooks (and redux and react-query) are bad (in my opinion) because they are easy to write with and hard to reason about.
I think that the React devtools haven't kept up with how hooks work. They let you look at your props and state as they are now but not look at the "timeline" like something like the Redux devtools do. My struggle debugging is always trying to look at intermediate states and only having the current app state available, pausing the debugger didn't work as the React devtools crash when you do that.
My gripes w/ Redux is that its pattern is clearly better suited to a runtime with more to offer functional programming than first-class functions, hence all the boilerplate and effort to abstract all that boilerplate, as well as deciding how to pick how you'll get to granular updates (which is where, from where I'm standing, React Hooks' design starts).
But in comparison to React Hooks, its vastly superior for affording clear separation of boundaries in an app.
So maybe I’m not the best person to speak specifically on redux as we use redux sagas so I can’t give a fair take on redux itself. However my gripe with redux sagas and react query is pretty simple, react query is implicitly defined implicit behavior, redux sagas is explicitly defined implicit behavior and xstate is explicitly defined explicit behavior.
What do I mean by that? The applications that I work on do a lot of composition of apis, reshaping the data and caching on the frontend since our backend teams are micro services teams. What happens is that our codebase needs to define multi api workflows and data transformations which we define using redux sagas or react query (we either adopt one or the other for the codebase).
The problem with the react query way of doing this is that the query has its own built in lifecycle it goes through stale -> fetching -> loading -> success/fail this is implicit behavior that you don’t have visibility into why it is making a transition. It becomes very painful for large workflows of api compositions to debug this kind of implicit behavior.
Redux sagas on the other hand is explicitly defined. We have these channels of communication over which messages are defined (actions) which cause some explicitly defined behavior to happen (sagas, reducers). Now why is this problematic? In a large codebase of micro services transactions we are dispatching actions in response to a parent action forming an explicitly defined implicit behavior.
So for example with react query if I am debugging an unexpected state in prod for a bug I have to reason about the way react query is processing a query in order to debug it due to the implicit behavior. In redux sagas world I have to reason about the implicit state machine of actions, sagas and reducers that a workflow kicks off.
This is why I don't like these two styles of organizing the application state. It becomes difficult to reason about the behavior but the redux/redux sagas way is easier to reason about because at least we explicitly defined the implicit behavior.
Also make sure to note I am talking about a complex web app on a micro-services api (frontend does a lot of api composition work), I am sure these are elegant solutions in the right context.
> What do I mean by that? The applications that I work on do a lot of composition of apis, reshaping the data and caching on the frontend since our backend teams are micro services teams.
You may want to consider whether you're doing the back end devs' work for them. :) I'm a FE dev working with a lot of microservice back ends for the last 3 years, and a realization that I had early on was that the back end devs were reducing the back end complexity by pushing it into the front end. Whenever I find myself writing transaction logic for an operation that cuts across multiple services, I suggest we use a backend for frontend (BFF) service that performs those transactions instead. There is an incredible amount of complexity in the front end already, and things like read/write transactions and data consistency are not front end concerns. At my current gig we use an event driven saga architecture[0] (inspired by but no relation to redux-saga) to keep multi-service transactions out of the front end, and it just feels like everything is where it should be. Instead of using a front end saga, the front end sends a graphql mutation to a back end saga and gets back a transaction id, which it can subscribe to and get status updates on the transaction (ie. state and errors). It wouldn't make sense to have your back end devs manage the state of a dropdown, so why are they always asking us to manage their data transactions? :)
I heard that React is only the "view" but when actually trying to make an non trivial app (lots of async calls) logic and everything gets entangled. Please show me an app where react is only a view withot side effects
I also really like them but I fear that they are a bit magical, i.e. they solve a problem in a way that feels very simple but might end up blowing up in complexity once you look at them from a certain angle. Like when debugging certain state issues. They are very nice until they break in unexpected ways.
I feel that React is like.. 20% of the way to programming nirvana, and hooks are like 24% of the way, but it's very much just another step on the path. They're an inelegant expression of an elegant idea, which class components couldn't express at all.
I haven't really tried it but I would expect that Elm has solved at least some of those problems already. It feels like hooks are (another?) attempt at making react more monad-like but without having the type system to back it up.
I didn't like Hooks out of principle when I first read about them. I still get slight heebie-jeebies when I think about the fact that they rely on calling-order for identity. But having now used them for six months, I can't deny the practical benefits they bring.
I think it helps to not think of them as "just a way of letting you write all your components as functions". That was always a red herring; even before hooks, React "functional components" were impure stateful objects at runtime. Only your part of the code was ever kind-of pure. It was an illusion, and Hooks just pulled back the curtain.
I think of Hooks as more like a DSL for declaring externalities, which just happens to take the form of a series of function calls. They're clunky for building complex memoized value-graphs [gazes longingly at MobX], and complex useEffect behaviors can get really hard to follow real quick. But for building average web apps - where 90% of the time you're either pulling in standard externalities or defining little pieces of primitive state - they make things really ergonomic. They also enable some fancy optimizations and scheduling on the React side, from what I understand. Overall I think they were the right move for a for-the-masses UI framework.
> Hooks as more like a DSL for declaring externalities,
That's exactly right in my opinion. Hooks are the place to declare stateful and effectful (aka impure) logic. Using a hook is an equivalent signal to using a Future, or an IO in other languages like Scala.
Often combinations of useState and useEffect are best written as a custom hook to reduce noise in the containing component. An easy example can be seen in something like
They also eliminate subtle bugs that can emerge from `this` being mutable in classes, and they make the React lifecycle more intuitive. Rather than thinking about whether the component did mount, will mount or will unmount, you just associate side effects with certain states.
With all that said, I would still say I'm not really a fan of hooks. I more see them as a necessary evil than a great feature. I try to use them as minimally as possible.
Worth noting that mutable-this can be made to work by using something like MobX. It’s not a fundamental problem, just one that React declined to solve directly, which I can’t totally blame them for.
I agree that “separating concerns” is generally a good thing.
However, the issue is that the traditional division of concerns is more difficult to maintain in today’s web apps. Compared with web pages 20 years ago, web apps today are dense, interactive and complex. You might have dozens of UI components in a single page, each with their own piece of state, business logic and styling. Moreover, state, business logic, and presentation are oftentimes tightly coupled by design: eg, dragging this slider changes its shading using a complex algorithm.
Therefore, it’s becoming more advantageous to decouple individual ui components, each with their own state/logic/styling, than it is to, say, stick all of the state your web app deals with in a single place.
In other words, it makes sense to encapsulate all that code related to that crazy slider in one place, even if that includes state, styling, algorithm, etc…
> In the case of modern front end engineering and React especially, you can reduce everything down to two simple concepts... > Rendering the current state > Updating the state.
Not a fan of the negativity in this post (I use hooks all the time) but the advice to separate concerns about managing state and rendering the state is GREAT for ANY architecture.
Pedantic nit: [ readRequest, requestRead ] reminds me of one of my least favourite nitfalls from Django: FileField and FieldFile. Yes, the names make sense. But human brains mash these things up so regularly that I never seem to be able to permanently remember what's what. I'm always triping on them.
This also brings me to something I've been wrestling with a bit: Array unpacking allows you to pick your own variable names, but you also generally have to unpack everything if you want some later variables. Order matters. I find this is not very self-documenting and is less fun for autocomplete.
Object unpacking lets you pick and choose what pieces you need from the hook's interface, and they come pre-named, which is good but sometimes bad.
I think I'm settling on Object unpacking being the better pattern for my tastes.
I would argue that object unpacking is superior unless you have something similar to useState, where you only return two variables, and you know you'll never need the second variable without the first (which in practice may be very difficult to know beforehand, as requirements change).
Object unpacking also allows you to alias the variable names, though it's slightly more verbose: `const { foo: newName } = someThing()`.
You can also choose to forgo unpacking entirely and just use the natural namespacing returning an object would provide:
I like the `extendState` concept. So far, most of the time, I find that modern JS is fine enough. I can absolutely see the benefit of abstracting this concept. But I'm always hesitant to layer on top of an API just for little improvements. The cost is deceptively high: you have to re-remember your custom API and others have to learn your custom API.
Briefly - it is because React batches and executes setState calls some time in the future - thus you MUST use a function call to get a non-stale state.
Refer to my more detailed explanation elsewhere in this thread.
Thanks. I see the example in the docs shows this method. StackOverflow shows many people sharing the same. I’ve never had this problem so I’m probably quite lucky. But it makes complete sense. I’m surprised it’s not called out on the docs page for setState.
>> I’ve never had this problem so I’m probably quite lucky.
Possibly, but more likely you've had weird bugs that never quite made sense and eventually things seemed to work so you moved on, but what was actually happening was a stale state problem. It's hard to write big React apps using the wrong way to udpate setState without bugs.
A nice approach of separating concerns is having the API layer provide its own, already typed hooks and hook-backed primitives such as `useThing()`/`updateThing()` (or `API.thing.useData()`/`API.thing.update()`, etc.), which under the hood can do whatever necessary (including using shared logic, API-specific access request handling) while providing common primitives for tracking progress, cancellation[0] and other conveniences. With TypeScript, you can ensure those hooks are typed appropriately and don’t require callers to annotate.
Compared to approaches such as `setAsyncState(API.thing.get<ThingState>())`, which do seem clever, the former approach may be a bit more concise, and I’d argue more predictable (if I see a `setState()`, I’d rather not have to do a double take and reason whether or not it actually sets state where it says it would).
[0] The cancellation approach in the example in this article rubs me wrong, because the update request is not (and can’t really be) actually cancelled, so the thing may be updated without GUI state knowing.
You can certainly compose a `useThing` hook (and others) for more encapsulation and functionality specific to said thing.
We actually do this elsewhere as necessary, like with the top level `Store` component. For the most part, it depends on the situation and how concise/redundant (or not) you want to be. In many cases it actually ends up being more concise overall to simply compose `setState(readThing(id))` as needed, instead of creating a specific method for every possibility.
Surprised at the negative comments already. I'm not sure half of the commenters use React on a daily basis. I've re-written these same hooks in various ways, as well as used ones written by others. This look great to me, personally! I've been using a weird implementation of useReducer() (most commonly suggested on SO) but extendState() is a more elegant solution.
Couple suggestions:
* the repetition of "read" `const [ readRequest, requestRead ] = usePromise(read)` makes it hard for me to keep these things separate. It would be easier if you had a real life example, like "get users" or something but even somethin like `const [ apiState, fetchApiState ] = usePromise( apiRequest )` would be better IMO.
* `readRequest.cancel` - sometimes I've wanted to cancel a request but it's not an error to show the user, but it looks like the view wouldn't be able to tell the difference from a regular http error
* `readRequest.reset(`error`)` could be another way to just reset a single property, instead of having to use brackets
* uh where is the code for the hooks? A direct link to code or even a library would be great
In the design pattern mentioned at the end, there are some application state that are stored in both `updateRequest` and `state`, i.e. the data that is returned from the update response (stored in updateRequest) and is used to extend `state`. Having the same data stored in 2 different states seems error-prone. Which one is the source of truth when the 2 states accidentally get out of sync ? For example, nothing prevents extendState() to be called again to modify the `state` to something that is not the same as the value from update response.
The API response ends up being the source of truth, in this case. If you wanted to prevent the user from updating the internal state while waiting on the API, you could certainly do that with a check for `updateThingRequest.status === 'pending'`.
The initial writing for "useAsyncExtendedState" said "Don't use extendState for everything! Use it only when you know you need to merge a partial state.". However, the example doesn't use "setState" at all. This feels like a gap or tension in the API design—if setState doesn't get used in practice, why force users to include it? Instead, I think the default react setState pattern is much better, since it allows you to easily set *or* extend your state depending on your usecase:
In the examples, `extendState` is used because the API returns only the updated props when updating.
Your preference for only `setState` is definitely warranted. The source is available for this reason, and it's pretty compact. You can quickly remove the `extendState` portion if you want.
Something I often want to do is write a custom hook with internal state that will be used by two or three components - and what I really want to do is have consistent internal state for that hook, regardless of which component its being invoked from.
This obviously doesn't work with vanilla hooks, but is there a way to achieve this pattern? It seems like it would be so light and fast compared to a heavier solution
It's slightly overengineered in that not everything has to be in separate files; I just reduced this from an an existing example that was more complicated and had this file structure already.
Yes, with Context. You will need to wrap part of the tree with that Context's Provider, and then you can consume the context value with React.useContext inside a component. Your custom hook can also use this.
- It's also "official", ie, from the actual Redux team (myself, Lenz Weber, Tim Dorr)
- RTK simplifies common Redux patterns, but tries to stay "typically Redux". It doesn't hide the fact that you're using Redux. Libraries like Easy-Peasy and Rematch add additional levels of abstraction, to the point that it doesn't even look like Redux any more. I can understand why that might be appealing to some folks, but I think it's too much abstraction.
As mentioned elsewhere, this is invalid code - a bug waiting to show itself - you should never do:
setState({...state, a: 4 })
If all extendState is doing is concealing the underlying function call, it's saving very little boilerplate and adding the cognitive load and potential issues related to the wrapper.
Cognitive load adds over time and I think it's so much simpler to break down such loads into smaller pieces that are documented well and abstract away the complexities/syntactic nuances. In short I keep forgetting to spread the previous state and that led to bugs for me.
Edit: Yes, you are right about the stale state and that was one of the primary reasons for me to not make it a pattern everywhere.
The example you give above is incorrect code - it does not work the way you think - you need to update your understanding of React. React batches the useState calls and executes them later - so you must be very clear about the data you are using in the useState function - picking up the state value from the enclosing code gives you a state value that you cannot trust.
The ONLY reliable to way actually get the previous state is to use a function as the argument to setState.
In he example above, you think you are getting the current value of count, but it might be stale - this is critical to understand in React and if you don't understand it then you'll be fighting weird bugs forever.
If you wish to use existing state, when you pass an update function to setState then React ensures that the previous state argument passed in is in fact the previous state.
I think that's the correct brackets but HN is not an IDE.
The rule is simple: if you are updating state, ALWAYS pass an update function.
The reason I have an issue with the topic of this HN post is that updating state is critically important in React and you should understand how it works and do it correctly, with a function call that uses previous state. useState with a function call is not boilerplate to be abstracted away, it's a simple and unambiguous way of writing React code and is central to writing React apps correctly - don't hide this.
I have a custom hook that I like to use called useLocalStorage which is just a wrapper around useState which first looks for existing state in localStorage. There is a defaultState argument if there's nothing in localStorage.
Thanks for sharing your post. I want to highlight that react-query solves all of those problems very elegantly. You should check it out if you haven't already.
179 comments
[ 3.9 ms ] story [ 205 ms ] threadAlthough, when I see code snippets on the internet I always wonder how do people test this.
The fact that dependencies are declared as imports and all the logic is inside the component function, it feels like you have to shuffle things around just to make the thing runnable outside of React.
Of course, you need to write your components in such a way that they are easy to isolate. You can also make use of composition and maybe even mock out components.
Testing the components themselves is ample.
You can find the tests for these hooks here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216...
Is that what you're asking about?
In isolation, unless you have very complex hooks, it’s a bit like you’re testing react or JavaScript themselves. When testing a component which uses the hook, I find you get to test the behaviour and expectations a little better - it’s close to a real use case.
Also, take a look at Redux Toolkit Query and React Query. Both provide advanced query state management based on hooks.
The issue with that is that sometimes you need to make transactional changes to your state (i.e. either update 3 variables together, or not update them).
That gives you 2 options: 1. bundle the state up in a complex single object with useState 2. extend the space state of your app to allow for mid-transaction rendering (i.e. only 2 of the vars have updated and the 3rd has not).
#2 feels a lot harder to me, tbh (although I do strongly dislike #1. and would like an alternative.)
In React 18 I believe this is mostly fixed.
There are exceptions I’ve forgotten but generally it solves this problem well.
Are you referring to the usePromise and useAsyncExtendedState? If so, it's okay to use those hooks independently. They work fine without each other, but they also work well together.
However, it "clicked" with me when I saw how they use their usePromise hook (to me it seems like their useAsyncExtendedState hook is only really useful in conjunction with their usePromise hook). That is, it is extremely common in React to call some remote API, then update the state when the Promise resolves. You also have a common set of things you want to handle: showing that the request is still pending, handling an error, updating a part of the state with some subset of the response object when the Promise resolves, etc.
It's very possible to do this in React without these custom hooks, but you'll usually find you have top-level state variables in your component that mirror the success/error/pending states. You need to do that all over the place.
Using these custom hooks makes it easy and consistent to make these remote calls but handling the stuff about the remote Promise request is essentially "contained" by the result of usePromise. Seems like a really nice, clean way to get consistency across remote calls in all your components.
setState((previousState) => ({...previousState, foo: false}))
({ contact: { address: "" } })
as opposed to
({ ...prev, contact: { ...prev.contact, address: "" } })
Given the way hooks are structured, if I want to use both `setState` and `extendState` in a `useCallback`, I'd need to pass both to the dependency array, which is just extra effort (which defeats the purpose of the syntactic sugar imo).
Often I'll throw in a few helper functions in a useMemo, too, and then provide the whole thing through Context. It's like a mini-Redux for some section of the app.
Also, the setters are usually passed around all over the place, so no, I don't think the point is centralizing component state at all.
> It's not mutable during the render function
I don't think that's the point at all when people talk about mutability. To me mutability is just a proxy for "lacking access control", which again goes back to the point about centralizing state updates. So arguing about the technicality of what is mutable and what is not is pointless.
As others mentioned useImmer is a more bulletproof solution then assuming your state can always be modeled by a single layer Object though.
Actions make state more debuggable. Selectors make rendering more performant and often help structure complex rendering logic. Also, Redux is a way of not having to define asynchronous control flow by composing components, which can get really messy.
Granted, it's not necessary for every app.
And with Typescript, it's not a bit unpleasant to work with.
We'll probably open source portions of it as well eventually, but first we're giving it a huge UI overhaul.
Another day, another React team coming to the belated realization that hooks are an inelegant solution to problems that have already long been solved. Separate your view layer from your application state. Get all those network calls the hell out of there. Relegate your components to simple stream transformers -- props in, HTML out. If you're doing anything other than that, your function components aren't really pure functions.
It's frustrating to watch an entire swath of the industry continually rediscover its own inadequacies year after year...
It is actually separated, not from React though because React will need the state (data) at some point anyway. We'll go into more detail on that in the next post. To touch on it briefly here, shared application state exists on its own at the top level of the app, as a composition of stateful hooks. See here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216...
Every possible API request also exists on its own outside of anything React (view). See here: https://github.com/Molecule-dev/molecule-app/tree/6e2456e216...
Or for a more specific example, see this API resource route index: https://github.com/Molecule-dev/molecule-app/blob/6e2456e216...
I may be misunderstanding your complaints though. I appreciate your feedback.
> Every possible API request also exists on its own outside of anything React
That's good, but I would take it once step further and disallow any React component from directly invoking those methods.
Of course you're always going to need to do something asynchronously when the user clicks a button or what have you. But in my opinion, it's a lot more maintainable to have that just be an event that the component fires, and then have something else listening for that event out-of-band (and then sending the network request / updating the state to say "connecting" or "request failed" and so on). What happens in async land is not really a pure component's business.
(I know I'm definitely out of lock step with the current React ethos on this, so permit a crusty neckbeard his pet gripes.)
The React ecosystem has spent the last few years trying a model where the application layer separated from the view layer with a pure functional state management solution called Redux. The overwhelming response? People didn't like it.
Decoupling systems is a trade-off. Pull your network requests out of your components and you have two bits of code that are easier to test. Indirectly, the component is still going to call that code, and it's up to you to manage the complexity of that indirection.
Not every application needs separation of concerns, and in many, colocation of concerns reduces the cognitive burden, because you can reason about components in isolation. To me, that's a more powerful guarantee than a function being strictly pure.
I like (and use) Redux on a daily basis, and I don't think it's a sensible choice for lots of React apps. Maybe the overwhelming rejection that I've witnessed is people discovering that they used it for stuff they shouldn't have.
It won the Flux wars, but there are state management approaches based on paradigms other than flux. There's at least the model that Recoil/Jotai uses (atoms?), the model that MobX/Valtio uses (mutable observable state), the model based on state machines (XState), the model heavily using React context (Constate), etc.
The churn in the frontend ecosystem reminds me a lot of fad dieting: people have never really experienced working in a paradigm that enables them to stay healthy through regular diet and exercise over the long term, so they turn to the latest shiny gizmo hoping that this time it will be different.
For any project that I'm responsible for, I don't feel comfortable designing around a paradigm unless there are some guard rails to prevent developers on my team from accidentally tying the code in knots. How enjoyable is this for the developers? Are they sprouting angel wings and playing the lyre as they write code in iambic pentameter? Probably not, no. They have less freedom of motion than if they were left to their own devices.
This is a larger debate: where to fall on the spectrum between unadulterated developer bliss and having an application that is still maintainable in 5 years. I don't put much stock in developer bliss, but I do appreciate that the sword cuts both ways.
Redux is still by far the most widely used state management tool with React apps (my estimates are around 45-50% of React apps use Redux), and we try to give clear guidance in our docs on when it does and doesn't make sense to use Redux.
FWIW, we get highly positive feedback on a daily basis from users who tell us how much they love using Redux Toolkit.
Resources:
- https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet...
- https://blog.isquaredsoftware.com/2021/01/context-redux-diff...
- https://blog.isquaredsoftware.com/2021/05/state-of-redux-may...
- https://blog.isquaredsoftware.com/2021/05/learn-modern-redux...
- https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
- https://redux.js.org/tutorials/index
- https://redux.js.org/tutorials/essentials/part-2-app-structu...
Using the redux-toolkit and the hooks, with none of the additional junk is a great experience.
I have respect for what acemarke writes above and I think acemarke is right that redux has been overused a whole lot.
A bit sad that immer was used (a bit too much magic for my taste), but I can understand the reasoning, and if you just accept it, it's ok.
Thank you and all the other maintainers for your work!!!
https://news.ycombinator.com/item?id=29599980
- Too much "magic" as you said
- Proxy-wrapped data values can be harder to inspect in the browser's debugger
- Seeing "mutating" code may be confusing
I understand those, but from my perspective the benefits of Immer far outweigh any potential negatives.
Ultimately all Immer does is track attempted mutations to nested data, and replay them to create the immutable updates as if you'd done all the nested spread operators yourself. There were dozens of immutable update libs in the ecosystem already (I had listed most of them in my Redux addons catalog), and accidental mutations have always been the number 1 cause of bugs in Redux apps. So, a better way to write immutable updates was clearly valuable.
Immer drastically simplifies all that update logic:
- Removes all the extra object spreads and slices and concats you would have had to write previously, so the code is much shorter and easier to read. It's also more clear what the actual intent of the state update is.
- It also completely eliminates accidental mutations _in_ reducers, and also _out_ of reducers by freezing the state.
So, shorter code, easier to read, and eliminates the #1 cause of Redux bugs.
The tradeoffs are a couple KB of bundle size (which is quickly paid for by having shorter reducers), and needing to understand that Immer is there and doing this "magic" for you.
The one still slightly nagging annoyance is that inspecting Proxy-wrapped data in a debugger is painful, but Immer does supply a `current()` util to extract the plain JS data if you need to, and we document doing that:
https://redux-toolkit.js.org/usage/immer-reducers#debugging-...
So, as with all tools Immer isn't 100% perfect, but it was absolutely the right choice to include it in RTK by default.
But as I said, I still highly recommend Redux Toolkit. After dealing with the code using MobX and the complexities brought by its reactions, I would take Redux anytime. Immer is just a small unfortunate detail, but I can live with it.
You still have to understand Redux's weird boiler plate concepts - actions, action creator functions, reducers, thunks, ... - to understand and use Redux toolkit. Redux is broken from the bottom up.
Note that I would never (again) use thunk - using such side effects leads to unmaintainable code, at least in my experience.
I can't speak to his code contributions, but in terms of documentation, tutorials, and community engagement, most open source projects would be lucky to have similarly prolific contributors.
FWIW I _have_ written actual code in the libraries too :) I wrote React-Redux v7 by myself, shepherded the design of our hooks API in 7.1, and did all the coding work for v8. Also created Redux Toolkit, added a couple additional APIs, and a bunch of other stuff :)
These are all obviously _very_ imperfect proxies for actual usage stats, but they're all that I see available to work with.
FYI my last attempt to estimate "React state management market share" was in a Reddit thread earlier this year, with download numbers and caveats included:
https://www.reddit.com/r/reactjs/comments/lcgqnd/state_manag...
The biggest thing I miss in react ecosystem is decent redux ORM. https://vuex-orm.org is just so great for so many use cases (agree that it might an antipattern in many situations). Is there any chance that https://github.com/redux-orm/redux-orm, which was actually what inspired vuex-orm, would get more love from anyone to become an actively maintained library?
Thanks
At the time, the main benefits to using Redux-ORM were easier immutable updates for items in the store, keeping items in a normalized state structure, and managing relationships between items for both lookups and updates.
Since then, Redux-ORM has changed maintainers and had its API tweaked. Meanwhile, we added a `createEntityAdapter` API [0] to RTK, which handles normalization and some CRUD-style updates to the data. Even without those CRUD helpers, you can still do `state.entities[itemId].someValue = 123` thanks to Immer, so immutable updates are taken care of.
At this point, the one remaining thing that Redux-ORM really handles that RTK doesn't is that specifically relational behavior - ie, given a `Post`, look up all its `Comment`s by an array of IDs or something. You can do a decent amount of that with selectors, but yeah, Redux-ORM's API here is nicer.
We haven't tried to provide any of that ourselves because this becomes a much more complicated and app-specific topic. But, I'm always open to suggestions for APIs and use cases. If you have ideas for things that we should consider adding to RTK, please do file an issue so we can discuss details!
[0] https://redux-toolkit.js.org/api/createEntityAdapter
It won't ever be a one-size-fits-all solution and unfortunately, Redux will have to live with the legacy of plenty of users finding that out the hard way.
That being said, I think Redux Toolkit is a great improvement to the vanilla experience and I'm always impressed by how much I see you out and about on the internet weighing in on these kinds of threads. Inspiring stuff!
That's not a bad thing (and it's better than the alternative of not caring for design patterns whatsoever). It's part of the learning process, and since there are so many beginner developers who are using React, their perspective is much 'louder' than in other dev ecosystems.
You can see this in the absurd amount of introductory-level React content posted to Medium, DevTo, Twitter, etc. This has bred a very strong 'follow-the-leader' culture where, when the one person is the room who _does_ know what they're talking about makes a statement, others will repeat it verbatim without understanding its nuances due to a lack of experience/context.
Redux suffered heavily from this. New React devs in 2017 were faced with mountains of tutorials which all used Redux. Many of these tutorials were written by other newbies. Your mental model of React dev was then shaped around Redux. Therefore you would put everything you could into the Redux store, which is a bad idea -- you usually don't need form state in there, for example.
Then some React thought-leaders saw this problem and inadvertently created a counter-movement by raising how Redux was overkill for some use cases, which was misconstrued as 'you shouldn't use redux _at all_'. The pendulum has been swinging back and forth ever since.
Yes, not every application needs separation of concerns. But some definitely do. It's not black and white, and that unfortunately means there's no definable 'best practice' that can be tweeted or blogged about and followed blindly -- it just has to be learned from experience.
Or maybe more accurately, discovering that it's often simpler to separate by concern at the component level, than at the app level.
We all ride the pendulum until we find the shade of grey which works best for us.
Why's that? IME it is usually simplest to just put everything in redux. For example, if you have a form under some kind of tab navigation thing, you'd ideally want the form state to be preserved even when they tab out and then back in. Putting it in redux means you don't really have to think about it
You'd still preserve on tab change just as easily, while using just reacts mental model.
Like, I don't want _everyone_ to use Redux. I just want people to understand "here are the strengths, weaknesses, and use cases of tools X, Y, and Z, and when it may make sense to use them".
Honestly, we Redux maintainers (Dan, myself, Lenz Weber) have had to spend far more time telling people when _not_ to use Redux than we have trying to advertise the library :)
When I rewrote the Redux docs tutorials last year, I made it a point to put "When should I use Redux?" right up front on the first page:
https://redux.js.org/tutorials/essentials/part-1-overview-co...
but that sadly can't have any effect on the thousands of existing tutorial pages or low-effort posts that are out there already.
I've been reading "blue" DDD book (by Eric Evans) and "red" book (by Vaugh Vernon) and that was a completely "my whole life was a lie" type of experience and relief at the same time. It's just so great to have the principles of who to structure the code. It, by definition makes, your codebase structure meaningful. Because it's structured according to some common knowledge, not your random thoughts at the time you were writing code.
I was surprised to find so little DDD react sample codebases. Let's say for backend there is huge amount of samples, i.e. https://github.com/kgrzybek/modular-monolith-with-ddd . For react/frontend I have bookmarked only https://github.com/talyssonoc/react-redux-ddd/tree/master/sr... and few more, but those others does not meet the optional criteria i like really much - at the highest (or at app) level all codebase need to have folders app, domain, infra and ui. Simple rule, but simplifies life a lot.
So my question is - is DDD for some reasons not very applicable for app frontend development. Or it just never became popular. Or maybe DDD is popular amongst react developers, just I am not aware of this.
Many thanks for any ideas and comments!
Remember: components using hooks are pure functions! That's what makes them so effective.
https://overreacted.io/algebraic-effects-for-the-rest-of-us/
Yes, and given the same outputs provided by props + hooks, components will return the same value. That's why you can write hooks functionally, and why we can do away with class-based functions entirely. With the exception of refs, there is no actual internal state that your component needs to deal with.
The necessity(!) to wrap each and every basic JavaScript API (useEffect v. setTimeout, useLayoutEffect v. queueMicrotask, useInterval and usePromise v. using Intervals and Promises) does not at all showcase the "expressive power" of React.
It was fun while it lasted but it's time to let go. React's become JQuery
useEffect works by running on each render (no dependencies) or running when rendering and dependencies changed. Where does a timeout factor in?
useLayoutEffect also doesn't work based on the microtask queue - it uses React's internal scheduler, which I would imagine makes sense in the context of the library. Replicating it with queueMicrotask is probably not a trivial task.
React deliberately ships with only a few hooks, more or less the fundamental ones that you cannot yourself implement. They left the rest up to the community to explore, because it is very difficult to predict how a new paradigm will be used. Hooks isn't just useState and useEffect, hooks are a way of writing reactive logic.
It is very impressive that the community is able to produce new tools that simplify annoying problems like managing asynchronous state with just a single function, in a way that can be more-or-less dropped into any existing component.
This is insane. You shouldn't need to download useConsole from npm.
What? I don't think you understand hooks very well. You can already use promises with hooks by creating them and then calling `setState` however you want in the callbacks to deal with pending/success/error. It's such a common pattern that the community has developed a `usePromise` hook that abstracts away all that logic for you, meaning that you can treat any promise as a stateless value to be consumed by your components, one that will automatically manage its state for you and guarantee your UI is consistent without any additional effort. I never have to deal with managing asynchronous state ever again! That's what a good library does: allow you to build reusable components that abstract away complexity. Hooks let you abstract away state management, the bane of UI development.
I can tell you from experience dating back to Backbone and Marionette that it has never been so easy to deal with async state before. Coupled with Typescript, I also get the guarantee that I've accounted for every possible case (success, pending, error). It's a zero-cost abstraction.
What's your experience with hooks?
Your last statement is completely wrong, though.
ReactJS/hooks are not zero-cost abstractions. They impose a cost both in terms of real performance as well as cognitive complexity.
And when you run into performance issues you're almost certainly confronted with this pattern of escalation (assuming you've done nothing wrong):
1. Litter your Code with calls to useMemo/useCallback.
2. Re-evaluate your business needs because you've exhausted the hooks "primitive".
3. Iteratively and painstakingly force more and more logic out of ReactJS' scope until you've met your benchmark targets.
Following along quickly requires intricate knowledge of poorly documented but overwhelmingly complex ReactJS internals alongside(!) whatever knowledge of JavaScript internals is required.
You have to have and process 2 complex, unique and outright byzantine models in your mind simultaneously.
Compare this to Angular(!) which lets you use standard web APIs, additionally exposes useful standard(!) primitives, and(!) provides you with straight-forward(!) opt-out mechanisms.
Yet here we are, quite literally downloading useInterval from npm 40,000 times a week.
The fact that hooks like usePromise and useInterval can be trivially written on top of standard web APIs and guarantee that your component hierarchy will update consistently without dealing with internal state is a groundbreaking achievement. I think you are focusing too much on the fact that hooks like usePromise exist, instead of on what they enable.
Have you ever used usePromise, and seen how it turns a stateful object into a stateless one? It has never been so easy to encapsulate asynchronous state.
- The ensuing paragraph suggests you are conflating Angular with AngularJS. Angular is build on top of proper, standard FRP and stream processing primitives. It is "reactive".
- Angular is in widespread use in the enterprise world.
- The reason ReactJS is much more prevalent everywhere else is it has no barrier of entry. None at all. This, however, is irrelevant with regards to the issue I am raising. I am complaining about the complexity ReactJS dumps on working professionals.
- I am not advertising Angular. I am telling you that EVEN Angular manages to suck less.
> The fact that hooks like usePromise and useInterval can be trivially written on top of standard web APIs ...
Again, not: "can". "Must"!!
Unless you mock a hook at the module import level (which is hacky), you lose the easy testability of your component. That's long been bothering me about hooks, although I use them plenty.
> If you're doing anything other than that, your function components aren't really pure functions.
Nobody claims they are pure functions. They aren't - but that doesn't really matter. Its an API choice that encourages productivity while still having tools for the abstraction that you are advocating for.
State hooks can be seen as a sibling of props - both are inputs to the component, but whereas props "push" data into the component, hooks "pull" data from somewhere else. This _is_ separating concerns - when a component needs data that isn't inherently something the calling parent owns, then making it a prop would just be forcing that component's concerns up the tree. Often it is better for it to just be an impl detail.
I don't find the article here super compelling, and as a general practice it is often better to author your own custom hooks that encapsulate things like the underlying HTTP request away so that your component is only dealing with its own concerns, but functionally there's no difference and its just a matter of how you split up your code.
What other costs are there besides boilerplate?
Let's say I need a single bit of state in my component. If I store that in a local `useState(false)` and update it with `setState(newValue)`, it is extremely easy to write, and also follow what is happening and how it is happening.
But if dogmatically say that this is poor encapsulation - this state should live in some Redux store, with actions and action creators, reducer functions, etc. there is inherent complexity, and I think in the case of a single bit used in a single place that it is pretty easy to see that the complexity is disproportionate to the actual needs at hand.
But the way I see it is a sliding scale - your state starts to become more complex? or maybe how the state is updated requires some additional business logic? Maybe you want to start using that state in different places? Any of these reasons can be cause to introduce abstraction, but my preference is to introduce the smallest abstraction that achieves the benefit you're after.
Sometimes this abstraction is hoisting state up - now your component doesn't manage its own state, but rather it communicates with its parent via props+callbacks. Now its a little harder to follow (need to consider this component, its parent, and how the prop/callback are being used), but for that complexity we now get to share that state with sibling components.
Sometimes this abstraction is moving it to a reusable hook. Now you have a similar scenario where to understand how things are working you have to understand the component and the hook, but for that cost we can now share business logic.
Sometimes that abstraction is moving it into a redux store, with all the boilerplate that goes with it. For the right use case this is not a bad thing, but I would only want to do this if I'm getting a proportionate amount of value for the increased cost.
I now really dislike hooks because of the amount of implicit behavior and useEffect poisons the codebase to add even more implicit behaviors/side effects to your application.
They make the rendering behavior really difficult to reason about. I caught a bug where you had to click a button twice and the reason why it happens is extremely difficult to reason about: https://stackoverflow.com/questions/58106099/react-onclick-n...
In my opinion what we need is an explicit state machine of the rendering of a component like vue’s component lifecycle. And these hooks we are using for state management we should instead use xstate to make the state transitions of a component explicit. React hooks (and redux and react-query) are bad (in my opinion) because they are easy to write with and hard to reason about.
My gripes w/ Redux is that its pattern is clearly better suited to a runtime with more to offer functional programming than first-class functions, hence all the boilerplate and effort to abstract all that boilerplate, as well as deciding how to pick how you'll get to granular updates (which is where, from where I'm standing, React Hooks' design starts).
But in comparison to React Hooks, its vastly superior for affording clear separation of boundaries in an app.
What do I mean by that? The applications that I work on do a lot of composition of apis, reshaping the data and caching on the frontend since our backend teams are micro services teams. What happens is that our codebase needs to define multi api workflows and data transformations which we define using redux sagas or react query (we either adopt one or the other for the codebase).
The problem with the react query way of doing this is that the query has its own built in lifecycle it goes through stale -> fetching -> loading -> success/fail this is implicit behavior that you don’t have visibility into why it is making a transition. It becomes very painful for large workflows of api compositions to debug this kind of implicit behavior.
Redux sagas on the other hand is explicitly defined. We have these channels of communication over which messages are defined (actions) which cause some explicitly defined behavior to happen (sagas, reducers). Now why is this problematic? In a large codebase of micro services transactions we are dispatching actions in response to a parent action forming an explicitly defined implicit behavior.
So for example with react query if I am debugging an unexpected state in prod for a bug I have to reason about the way react query is processing a query in order to debug it due to the implicit behavior. In redux sagas world I have to reason about the implicit state machine of actions, sagas and reducers that a workflow kicks off.
This is why I don't like these two styles of organizing the application state. It becomes difficult to reason about the behavior but the redux/redux sagas way is easier to reason about because at least we explicitly defined the implicit behavior.
Also make sure to note I am talking about a complex web app on a micro-services api (frontend does a lot of api composition work), I am sure these are elegant solutions in the right context.
You may want to consider whether you're doing the back end devs' work for them. :) I'm a FE dev working with a lot of microservice back ends for the last 3 years, and a realization that I had early on was that the back end devs were reducing the back end complexity by pushing it into the front end. Whenever I find myself writing transaction logic for an operation that cuts across multiple services, I suggest we use a backend for frontend (BFF) service that performs those transactions instead. There is an incredible amount of complexity in the front end already, and things like read/write transactions and data consistency are not front end concerns. At my current gig we use an event driven saga architecture[0] (inspired by but no relation to redux-saga) to keep multi-service transactions out of the front end, and it just feels like everything is where it should be. Instead of using a front end saga, the front end sends a graphql mutation to a back end saga and gets back a transaction id, which it can subscribe to and get status updates on the transaction (ie. state and errors). It wouldn't make sense to have your back end devs manage the state of a dropdown, so why are they always asking us to manage their data transactions? :)
[0]: https://github.com/social-native/kafka-sagas (this is a purely back end library; the front end queries the saga state with plain graphql operations).
I think it helps to not think of them as "just a way of letting you write all your components as functions". That was always a red herring; even before hooks, React "functional components" were impure stateful objects at runtime. Only your part of the code was ever kind-of pure. It was an illusion, and Hooks just pulled back the curtain.
I think of Hooks as more like a DSL for declaring externalities, which just happens to take the form of a series of function calls. They're clunky for building complex memoized value-graphs [gazes longingly at MobX], and complex useEffect behaviors can get really hard to follow real quick. But for building average web apps - where 90% of the time you're either pulling in standard externalities or defining little pieces of primitive state - they make things really ergonomic. They also enable some fancy optimizations and scheduling on the React side, from what I understand. Overall I think they were the right move for a for-the-masses UI framework.
That's exactly right in my opinion. Hooks are the place to declare stateful and effectful (aka impure) logic. Using a hook is an equivalent signal to using a Future, or an IO in other languages like Scala.
Often combinations of useState and useEffect are best written as a custom hook to reduce noise in the containing component. An easy example can be seen in something like
Seeing that you can easily imagine how it would be implemented: Almost always if there's a useState that has an associated useEffect, it should be turned into its own custom hook.Dan Abramov has a nice write up on the pitfalls of mutable this: https://overreacted.io/how-are-function-components-different...
With all that said, I would still say I'm not really a fan of hooks. I more see them as a necessary evil than a great feature. I try to use them as minimally as possible.
However, the issue is that the traditional division of concerns is more difficult to maintain in today’s web apps. Compared with web pages 20 years ago, web apps today are dense, interactive and complex. You might have dozens of UI components in a single page, each with their own piece of state, business logic and styling. Moreover, state, business logic, and presentation are oftentimes tightly coupled by design: eg, dragging this slider changes its shading using a complex algorithm.
Therefore, it’s becoming more advantageous to decouple individual ui components, each with their own state/logic/styling, than it is to, say, stick all of the state your web app deals with in a single place.
In other words, it makes sense to encapsulate all that code related to that crazy slider in one place, even if that includes state, styling, algorithm, etc…
Not a fan of the negativity in this post (I use hooks all the time) but the advice to separate concerns about managing state and rendering the state is GREAT for ANY architecture.
This also brings me to something I've been wrestling with a bit: Array unpacking allows you to pick your own variable names, but you also generally have to unpack everything if you want some later variables. Order matters. I find this is not very self-documenting and is less fun for autocomplete.
Object unpacking lets you pick and choose what pieces you need from the hook's interface, and they come pre-named, which is good but sometimes bad.
I think I'm settling on Object unpacking being the better pattern for my tastes.
Object unpacking also allows you to alias the variable names, though it's slightly more verbose: `const { foo: newName } = someThing()`.
You can also choose to forgo unpacking entirely and just use the natural namespacing returning an object would provide:
const thing = someThing() thing.foo() thing.bar()
If you wish to use the previous state you must do it this way:
setState(prevState => ({...prevState, foo: "bar"}))
Is there some race condition about updates or bulk updating?
Refer to my more detailed explanation elsewhere in this thread.
Possibly, but more likely you've had weird bugs that never quite made sense and eventually things seemed to work so you moved on, but what was actually happening was a stale state problem. It's hard to write big React apps using the wrong way to udpate setState without bugs.
Compared to approaches such as `setAsyncState(API.thing.get<ThingState>())`, which do seem clever, the former approach may be a bit more concise, and I’d argue more predictable (if I see a `setState()`, I’d rather not have to do a double take and reason whether or not it actually sets state where it says it would).
[0] The cancellation approach in the example in this article rubs me wrong, because the update request is not (and can’t really be) actually cancelled, so the thing may be updated without GUI state knowing.
We actually do this elsewhere as necessary, like with the top level `Store` component. For the most part, it depends on the situation and how concise/redundant (or not) you want to be. In many cases it actually ends up being more concise overall to simply compose `setState(readThing(id))` as needed, instead of creating a specific method for every possibility.
The second hook is given freely by 'react-query', the first one is done little bit differently
Couple suggestions:
* the repetition of "read" `const [ readRequest, requestRead ] = usePromise(read)` makes it hard for me to keep these things separate. It would be easier if you had a real life example, like "get users" or something but even somethin like `const [ apiState, fetchApiState ] = usePromise( apiRequest )` would be better IMO.
* `readRequest.cancel` - sometimes I've wanted to cancel a request but it's not an error to show the user, but it looks like the view wouldn't be able to tell the difference from a regular http error
* `readRequest.reset(`error`)` could be another way to just reset a single property, instead of having to use brackets
* uh where is the code for the hooks? A direct link to code or even a library would be great
Also, you can call `cancel()` (with no arguments) if you don't want an error.
You can find the code for the hooks here: https://github.com/Molecule-dev/molecule-app/tree/_e745872f9...
I'll add a direct link to that too. I really appreciate the feedback.
Your preference for only `setState` is definitely warranted. The source is available for this reason, and it's pretty compact. You can quickly remove the `extendState` portion if you want.
This obviously doesn't work with vanilla hooks, but is there a way to achieve this pattern? It seems like it would be so light and fast compared to a heavier solution
EDIT: I threw together a small example if it helps: https://gist.github.com/chrisfosterelli/2e523b4beae43f056249...
It's slightly overengineered in that not everything has to be in separate files; I just reduced this from an an existing example that was more complicated and had this file structure already.
https://easy-peasy.vercel.app/docs/api/create-store.html
https://redux-toolkit.js.org
- I created Redux Toolkit :)
- It's also "official", ie, from the actual Redux team (myself, Lenz Weber, Tim Dorr)
- RTK simplifies common Redux patterns, but tries to stay "typically Redux". It doesn't hide the fact that you're using Redux. Libraries like Easy-Peasy and Rematch add additional levels of abstraction, to the point that it doesn't even look like Redux any more. I can understand why that might be appealing to some folks, but I think it's too much abstraction.
This feels like slightly the wrong scoping. Either let me call the wrapped function multiple times, and get multiple metadata objects:
Or push the wrapping down to the calling of the function:With `useState`:
With `usePromise`: To initialize the promise state similar to `useState('alpha')`:setState({ ...state, foo: 'updated' })
setState already allows you to extend the current state.
I don't get it.
Setting a state is assigning the entire state object in this case:
The next time i want to update that state (not replace it completely), I do: or setState(prevState => ({...prevState, a: 4 })) `for the sake of brevity, the extendState just hides the boilerplate away:
It's just a matter of convenience to me too. I just call it `updateState` in my projects.Edit: Yes, you are right about the stale state and that was one of the primary reasons for me to not make it a pattern everywhere.
It MIGHT work, but it is still incorrect code - you must always use an update function if you are updating state, it's not optional.
Let's put it another way to be clear. This code is using stale state, don't use stale state:
The example you give above is incorrect code - it does not work the way you think - you need to update your understanding of React. React batches the useState calls and executes them later - so you must be very clear about the data you are using in the useState function - picking up the state value from the enclosing code gives you a state value that you cannot trust.The ONLY reliable to way actually get the previous state is to use a function as the argument to setState.
In he example above, you think you are getting the current value of count, but it might be stale - this is critical to understand in React and if you don't understand it then you'll be fighting weird bugs forever.
If you wish to use existing state, when you pass an update function to setState then React ensures that the previous state argument passed in is in fact the previous state.
i.e:
should be: I think that's the correct brackets but HN is not an IDE.The rule is simple: if you are updating state, ALWAYS pass an update function.
The reason I have an issue with the topic of this HN post is that updating state is critically important in React and you should understand how it works and do it correctly, with a function call that uses previous state. useState with a function call is not boilerplate to be abstracted away, it's a simple and unambiguous way of writing React code and is central to writing React apps correctly - don't hide this.
From React's "Using the State Hook" docs:
> However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
https://reactjs.org/docs/hooks-state.html#tip-using-multiple...
The `extendState` method gives you the same convenience of `this.setState` but with hooks in function components instead of class components.
I was replying to the poster who was using:
and so I assumed they were already operating in a class component.