[HN Gopher] Two custom React hooks
___________________________________________________________________
Two custom React hooks
Author : loh
Score : 174 points
Date : 2021-12-17 13:30 UTC (9 hours ago)
(HTM) web link (blog.molecule.dev)
(TXT) w3m dump (blog.molecule.dev)
| mattwad wrote:
| 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
| loh wrote:
| That's a good call. I'll change those to better names.
|
| 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.
| brundolf wrote:
| I think everyone has their own implementation of usePromise; it's
| weird to me that it isn't included out of the box
| notpachet wrote:
| > 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...
| vpfaulkner wrote:
| 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...
| z3t4 wrote:
| 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
| codecurve wrote:
| This isn't a solved problem.
|
| 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.
| notpachet wrote:
| 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.
| catlifeonmars wrote:
| I'm curious if the statement "happy developers create
| better systems" holds any weight.
| acemarke wrote:
| 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.
|
| 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...
| robertcorey wrote:
| good to see mark fighting the redux fake news in the
| comments as always.
| monocosm wrote:
| How did you get to your estimation of 45-50%? That sounds
| waaaaay too large.
| larsnystrom wrote:
| According to npmjs.com react has 13.6M downloads/week and
| redux has 6.7M. So 45-50% sounds about right.
| sroussey wrote:
| Tip: If you want to increase your numbers relative to
| another package, ship more versions over the same time
| period.
| [deleted]
| ratww wrote:
| 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.
| Lhiw wrote:
| Has redux fixed its broken implementation of event sourcing
| and cqrs yet or is it still encouraging people to execute
| side effects in reducers?
| trulyme wrote:
| Kudos on Redux Toolkit! Anyone who complains about
| boilerplate with Redux has obviously not tried the Toolkit
| version. Very impressive.
|
| 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!!!
| pcthrowaway wrote:
| 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.
| what_is_orcas wrote:
| 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.
| vlunkr wrote:
| 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.
| eitland wrote:
| Even better is my experience in the project were I work
| now and were for the first time we are working to remove
| redux.
|
| I have respect for what acemarke writes above and I think
| acemarke is right that redux has been overused a whole
| lot.
| [deleted]
| toinbis wrote:
| Thanks for great comment! I have one question.
|
| 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
| swyx wrote:
| 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.
| codecurve wrote:
| 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.
| azangru wrote:
| > theres a reason it won the Flux wars.
|
| 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.
| zaksingh wrote:
| 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.
| codecurve wrote:
| 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.
| anchpop wrote:
| > 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
| notpachet wrote:
| 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.
| arvinsim wrote:
| Doesn't matter. Every developer will still be at the mercy
| of the whims of the majority unless you find your own
| niche.
| dgb23 wrote:
| Lowest common denominator.
| notpachet wrote:
| 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.
| whakim wrote:
| 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.
| notpachet wrote:
| 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.
| whakim wrote:
| 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.
| onion2k wrote:
| 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.
| toinbis wrote:
| Am wondering what react community thinks of DDD.
|
| 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!
| nfRfqX5n wrote:
| 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
| azangru wrote:
| > 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.
| ajkjk wrote:
| I love hooks. Just saying. They're so much better than what
| came before.
| worldsayshi wrote:
| 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.
| dstroot wrote:
| > 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.
| loh wrote:
| > 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...
|
| 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.
| notpachet wrote:
| 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.)
| loh wrote:
| 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?
| notpachet wrote:
| That's a fair request. I'll tinker with it the next time
| I need to spin up a weekend project.
| joshfee wrote:
| 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.
| octet1 wrote:
| > 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
|
| What other costs are there besides boilerplate?
| joshfee wrote:
| 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.
| faizshah wrote:
| 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.
| wldcordeiro wrote:
| 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.
| imbnwa wrote:
| Can you expand on Redux?
|
| 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.
| faizshah wrote:
| 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.
| brundolf wrote:
| 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.
| city41 wrote:
| 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.
|
| 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.
| brundolf wrote:
| 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.
| bern4444 wrote:
| > 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
| const [ name, setName, isValid ] = useValidatedState(state,
| validationFunction);
|
| Seeing that you can easily imagine how it would be
| implemented: const useValidatedState =
| <T>(initialState: T, validationFunction: (val: T) => boolean)
| => { const [state, setState] =
| useState(initialState); const [isValid, setIsValid] =
| useState(validationFunction(initialState);
| useEffect(() => { setIsValid(validationFunction(state)) }
| [state]); return [state, setState, isValid];
| };
|
| Almost always if there's a useState that has an associated
| useEffect, it should be turned into its own custom hook.
| phailhaus wrote:
| 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.
| mikojan wrote:
| Components using hooks are not pure functions.
|
| 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
| steve_adams_86 wrote:
| 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.
| joshfee wrote:
| 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.
| imbnwa wrote:
| A thought that occurs to me is that React Hooks are
| Facebook engineering's attempt at an Effect monad in JS
| that isn't well-typed
| kristiandupont wrote:
| Dan goes into the topic of algebraic effects in this
| excellent blogpost:
|
| https://overreacted.io/algebraic-effects-for-the-rest-of-
| us/
| imbnwa wrote:
| Thank you for this, will take a look
| Waterluvian wrote:
| 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.
| setState({...state, foo: "bar"})
| andrewstuart wrote:
| setState({...state, foo: "bar"})
|
| The code you presented is wrong - it won't work the way you
| expect. Or worse, it _might_ work the way you expect, by
| chance.
|
| If you wish to use the previous state you must do it this way:
|
| setState(prevState => ({...prevState, foo: "bar"}))
| Waterluvian wrote:
| Thanks. Most importantly, though: why?
|
| Is there some race condition about updates or bulk updating?
| andrewstuart wrote:
| 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.
| Waterluvian wrote:
| 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.
| andrewstuart wrote:
| >> 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.
| bayesian_horse wrote:
| 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.
| maest wrote:
| > 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.)
| joshfee wrote:
| This sounds like the use case for `useReducer`
| Jenk wrote:
| 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.
| steve_adams_86 wrote:
| 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.
| jakelazaroff wrote:
| 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.
| steve_adams_86 wrote:
| 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.
| bayesian_horse wrote:
| Sounds like a niche use case inbetween the scope of useState
| and something like Redux.
| tshaddox wrote:
| Aren't multiple setState calls in the same tick likely to be
| batched and only result in one new render with all state
| values updated?
| steve_adams_86 wrote:
| 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 mostly fixed.
| wereHamster wrote:
| 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.
| tomduncalf wrote:
| 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?
| johnday wrote:
| 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.
| hn_throwaway_99 wrote:
| 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.
| roastnewt wrote:
| 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.
| neolefty wrote:
| 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:
| const shallowMergeReducer = <T extends {}>(state: T, action:
| Partial<T>): T => Object.freeze({ ...state,
| ...action, })
|
| 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.
| loh wrote:
| 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.
| fastball wrote:
| I don't really understand extendState because it seems like
| somewhat pointless syntactic sugar for:
|
| setState((previousState) => ({...previousState, foo: false}))
| ui4jd73bdj wrote:
| I would imagine it would help more with nested objects.
|
| ({ contact: { address: "" } })
|
| as opposed to
|
| ({ ...prev, contact: { ...prev.contact, address: "" } })
| loh wrote:
| For deeply nested data, I would probably use `immutability-
| helper`. Many deep spread operators can get ugly and
| confusing pretty quickly, in my experience.
| zeroonetwothree wrote:
| I guess how you should handle that is ambiguous. Probably
| best to be explicit for that reason.
| loh wrote:
| Yep! It is syntactic sugar. I personally like it though
| because it results in slightly cleaner, more concise code.
| It's just easier on my eyes.
| what_is_orcas wrote:
| 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.
| TimMeade wrote:
| Excellent! Love the extended state.
| bayesian_horse wrote:
| 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.
| xixixao wrote:
| Redux is far from the suggestion, and imho unless you can
| greatly benefit from the actions boilerplate its overhead is
| not worth it.
|
| As others mentioned useImmer is a more bulletproof solution
| then assuming your state can always be modeled by a single
| layer Object though.
| bayesian_horse wrote:
| 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.
|
| Granted, it's not necessary for every app.
| handrous wrote:
| 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.
| 5e92cb50239222b wrote:
| 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.
| acemarke wrote:
| Heh, glad to hear that RTK is an improvement there :)
| tddhk wrote:
| Completely agree, also individual named state hooks are great
| for semantic reasons, they will make your code more readable
| and more easily maintainable.
| nsonha wrote:
| 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.
| steve_adams_86 wrote:
| They're much different than mutable variables with a
| different syntax. Changing them causes your component to
| render and reflect changes.
| nsonha wrote:
| 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
| bayesian_horse wrote:
| 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.
| nsonha wrote:
| 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.
| strogonoff wrote:
| 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.
| loh wrote:
| 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.
| null_deref wrote:
| Have you thought about using react-query?
|
| The second hook is given freely by 'react-query', the first one
| is done little bit differently
| tuan wrote:
| 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.
| loh wrote:
| 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'`.
| enjoylife wrote:
| 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.
| loh wrote:
| These patterns are used all throughout an email app built and
| released earlier this year: https://www.tricepmail.app
|
| We'll probably open source portions of it as well eventually,
| but first we're giving it a huge UI overhaul.
| imbnwa wrote:
| Could Hooks be construed as Facebook Engineering's attempt at
| modeling React as an Effect monad?
| lifeplusplus wrote:
| simple few concise meaningful lifecycles methods to vague
| handicapped overlapping infinite hooks, really outdone in the
| name of progress.
| knuthsat wrote:
| Hooks are great!
|
| 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.
| steve_adams_86 wrote:
| 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.
| loh wrote:
| > how do people test this
|
| 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?
| HuntingMoa wrote:
| react-testing-library provides a helper to test hooks in
| relative isolation - renderHook.
| gherkinnn wrote:
| Unless you're talking about library-level hooks, there's rarely
| a need to test them directly. I'd mostly consider them
| implementation detail.
|
| Testing the components themselves is ample.
| bayesian_horse wrote:
| 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.
| goblin87 wrote:
| For your first hook just do this instead:
|
| setState({ ...state, foo: 'updated' })
| twic wrote:
| Okay so if i have: const read = (id: string) =>
| API.client.get<State>(`things/${id}`).then(response => {
| return response.data }) const [
| readThingRequest, readThing ] = usePromise(read)
|
| And i want to read two things from the server:
| const first = readThing('alpha'); const second =
| readThing('beta');
|
| Then both will update the same readThingRequest, right? So the
| information in it will vary according to which one returns first
| or something?
|
| This feels like slightly the wrong scoping. Either let me call
| the wrapped function multiple times, and get multiple metadata
| objects: const [firstRequest, first] =
| readThing('alpha'); const [secondRequest, second] =
| readThing('beta');
|
| Or push the wrapping down to the calling of the function:
| const [firstRequest, first] = usePromise(read, 'alpha');
| const [secondRequest, second] = usePromise(read, 'beta');
| thrwy_918 wrote:
| 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
| ngoel36 wrote:
| Redux is a game changer here
|
| https://easy-peasy.vercel.app/docs/api/create-store.html
| chrisfosterelli wrote:
| This is what react context is for. You can create helper hooks
| to expose the context more conveniently.
|
| 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.
| thrwy_918 wrote:
| thank you!
| chrisfosterelli wrote:
| you're welcome! happy coding
| ishjoh wrote:
| When we need a custom hook to have consistent state across
| calling components we have always used a context:
| https://reactjs.org/docs/context.html#when-to-use-context
| frosted-flakes wrote:
| 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.
| Waterluvian wrote:
| 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.
| andrewstuart wrote:
| How is extendState different to setState?
|
| setState already allows you to extend the current state.
|
| I don't get it.
| mlnj wrote:
| Based on your other replies I think you got it already, but
| I'll just add it here for the sake of others.
|
| Setting a state is assigning the entire state object in this
| case: setState({ a: 1, b: 2, c: 3 })
|
| The next time i want to update that state (not replace it
| completely), I do: setState({...state, a: 4 })
|
| or setState(prevState => ({...prevState, a: 4 })) `
|
| for the sake of brevity, the extendState just hides the
| boilerplate away: extendState({ a: 4 })
|
| It's just a matter of convenience to me too. I just call it
| `updateState` in my projects.
| andrewstuart wrote:
| 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.
| mlnj wrote:
| Not sure what you mean by invalid code. This is working
| code. import { useState } from 'react'
| function App() { const [count, setCount] =
| useState({ a: 0 }) return ( <button
| type="button" onClick={() => setCount({ ...count, a:
| count.a + 1 })}> {count.a} </button>
| ) }
|
| 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.
| andrewstuart wrote:
| >> Not sure what you mean by invalid code. This is
| working code.
|
| 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_:
| <button type="button" onClick={() => setCount({ ...count,
| a: count.a + 1 })}>
|
| 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: <button type="button" onClick={() =>
| setCount({ ...count, a: count.a + 1 })}>
|
| should be: <button type="button"
| onClick={() => setCount(prevCount => ({ ...prevCount, a:
| prevCount.a + 1 }))}>
|
| 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.
| nightpool wrote:
| 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:
| const [state, setState] = useState({}); //
| replace setState({foo: 1, bar: 2}); //
| extend setState(currentState => ({...currentState, foo:
| 1}));
| loh wrote:
| 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.
| [deleted]
| flippinburgers wrote:
| Just use redux.
___________________________________________________________________
(page generated 2021-12-17 23:00 UTC)