Which always seemed a bit ironic, considering those libraries are presumably intended to make state management work better if you really have to do a lot of it (i.e., have a large product).
At MPOW we have a bunch of state-managing components for complex cases, like multi-page forms or API-backed grids with tons of filters, etc. They take some effort to get the hang of them, but after that they save large amounts of boilerplate.
React comes with useState, useMemo, and useCallback, which is actually enough, but it may be too low-level when you think e.g. in terms of a huge interactive form. It's easy to write your own useWhatever based on these which would factor out your common boilerplate.
I suspect HTMX also does not come with every possible battery included, judging by the proliferation of libraries for HTMX-based projects. Modularity is a strength.
it is enough in the sense that NAND gates are enough to build computers. Yes, you can write a complex application using only those, and yes it's easy to write hooks to keep the boilerplate low - although Context still feels like too much boilerplate - but as complexity grows it's quite natural to want to share state between distant parts of the application, and then you're left choosing between lifting staten and prop drilling (not great) or Context and massive, frequent rerenderings. Hence the need for third party state management solutions
Are you sure contexts causing rerenders is not solvable by moving useContext() calls into hooks that each return only the part of the context that is required and ensure reference equality of returned value (meaning any component will not have a reason to rerender unless there is an actual change in that part of the context)?
I thought about it more and my reasoning is as follows (I may be wrong):
— useContext() returns an object. This object is new any time anything in the context changes. If the object has a nested sub-object, it may be new as well even if it did not change, though I suppose it may depend on how context provider works.
— All components where you use that context therefore will render any time it changes. (Takeaway 1: apply loose coupling & high cohesion principle to contexts, such that if you use the context and it changes in any way there is a high chance the change is relevant to wherever you use the context.)
— The render at that stage may be fine[0], especially if contexts are nicely organized, but care is needed because a downstream child that receives a nested sub-object from the context may render as well even if the sub-object is unchanged but referentially new (unless the child is wrapped in memo() and the memo handles reference equality, which may well be what you meant). (Takeaway 2: always remember that JavaScript is full of pointers and referential equality is important in React.)
— However, if part of the context is useMemo()ed for reference equality before being passed to a child, then the child will not have a reason to render from other unrelated context changes.
[0] It may make sense not to use context in large numbers of downstream leaf components (e.g., not use it in an item rendered in a map, but use it in parent list and pass relevant props to list items).
This may be frustrating to deal with in a large project, but it may be that the effort put into organizing contexts strategically and using them with care would lead to a more solid, refactorable and reusable architecture compared to state sprinkled around the place as essentially an equivalent of global variables. It depends.
Not sure, assuming a change in context through useContext() directly counts as state change and memo does not prevent re-renders on state changes…
Generally, re-renders should not be a problem (assuming nothing changed for this component it is a no-op as far as its DOM is concerned), but that is a separate issue, I suppose. I did have to worry about re-renders on a few occasions (and it never feels great when you put effort into memoing each prop for reference quality, but something still causes a rerender from within).
This is my main complaint about React - the "just don't worry about rerenders!" model works well until it really does not, but then you're left with very little help from the tooling to understand and fix it: "why did this render happen" is still a surprisingly difficult question to answer, and if you really want to take control of this you have to very carefully micro manage useCallbacks, useMemos, memo(), probably lie about your useEffect dependencies, check every single hook, and hope that your dependencies do the same. In the words of Ben Lesh[1]: React is not a pit of success.
That said, I fear your solution would not work - your usePartOfTheContext() would re-render every time the useContext() inside did, not helping with avoiding re-renders. But if you only passed the part of context to descendants that use memo(), it _should_ work. Having children of context providers always use memo() is probably a good rule of thumb.
This uncertainty is why I find it much more productive to just slap shared state inside Jotai, so I can be reasonably certain that rerenders will have the smallest granularity without any more work.
I am very hopeful about the compiler, which should help a lot with this, freeing a lot of mental bandwidth, but also useEffectEvent() which will finally make useEffect sane.
> your usePartOfTheContext() would re-render every time the useContext() inside did, not helping with avoiding re-renders
If a hook returns the same value with a stable reference across renders, and it is passed as a prop to some downstream components, it does not matter whether the hook itself uses context or not: for downstream components, prop did not change and no render can be triggered.
Lately I have started removing these libraries were possible and the maintainability has improved a lot.
Thankfully at my latest place there was no one when I came and I haven't seen a more productive team doing frontend.