Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Simple way to share state using React Hooks and Context (github.com/diegohaz)
131 points by hazdiego on Feb 6, 2019 | hide | past | favorite | 80 comments



Well, that's nice. I guess the React community is aiming to try every permutation within potential state management solution space, I mean something have to work after all.


I remember when you had to choose between flux, redux, alt, reflux, flummox, fluxible, fluxxor, marty.js, fynx, MacFly, DeLorean.js, fluxify, fluxury, exim, fluxtore, Redx, fluxx... (no I'm not making those names up: https://github.com/kriasoft/react-starter-kit/issues/22)

Every single time somethings come up, in the JS word, they try to repeat one of the mistakes from the past and completly ignore everyone else solutions and experience. It's like a compulsion.

In a year or two, your dependancies will be a representative sample of all permutations of those solutions, and will have to be maintained in that state forever, incompatible, or abandonned. You'll open some of them, trying to fix it, but you'll notice they all use different coding style, best practices, libs, and tooling. The dev that fought you in comments saying it would be fine is nowhere to be found now.

Most JS devs don't care. They don't maintain their stuff. Appart from the stuff that get famous because made by great coders (no matter the language), most projects die silently. There is a terrible graveyard hidden behind the corporate wall. Java, C++, Python and even Ruby projects are standing well and alive, next to the countless bodies of the last generation of JS frankeinstein monsters that were hype-sold to upper management to create something that could have been done in PHP 3.

Then a new stuff will arrive in the JS space, with again the potential of creating more confusion, more technical debt. You will raise your voice, and the JS community will scream back at you that you don't know what's good, you want to live with cavemen, and everything is great. They will get angry, and never see any problem.

Then rince and repeat.


That, or you can continue to use redux, which has existed since 2015, is mature, and still by far the most widely used state management system for react.


Redux, made for very large scale applications. Most companies shouldn't use redux at all, just as Dan Abramov said several times that you probably don't need it.

For unknown reasons most front-end developers don't wanna hear that and love to start with mega complexity and boilerplate where just a simple flux store or a library like 'unstated' would suffice. Anything leaner than redux is very welcome for the average companies web app imao.


> Redux, made for very large scale applications. Most companies shouldn't use redux at all, just as Dan Abramov said several times that you probably don't need it.

As per my own comments, I agree strongly.

> For unknown reasons most front-end developers don't wanna hear that and love to start with mega complexity and boilerplate where just a simple flux store or a library like 'unstated' would suffice.

Not unknown, no. Because FB promoted Flux like it was something magical and never seen before, talked about store without providing best practices or reference implementations, and so many store implementations came out at the same time. And most tutorials, not knowing exactly what to recommand to provide some state to your tiny app, started all to promote one store or another. So people though it was mandatory.

It's like with react tutorials.

When I teach react, I make sure people start by just dropping a script tag, and use createElement. No JSX. No webpack. Most of the page is even static HTML.

Only once they got the basics covered, I build on that up to abstractions, tooling, best practices, etc.

That's not what you read online. Online, people drop some magic lines and say "see how this todo list example is easy ?", then let you die here.


I agree with you in terms of learning React. Not sure I agree in terms of app size. Yes, there's some boilerplate associated with Redux. But it's pretty simple, and I find the "one way data flow" model really does simplify thinking about how state transitions work, in a way that is beneficial even for small apps.


Redux does come with boilerplate, but I don't agree that's it's complex. IMO it's one of the simplest ways of managing state. I find it's useful for things like user authentication details which are global-state that doesn't correspond to a specific part of the app. And even small apps tend to have this.


Unstated (last commit from Summer '18) seems unmaintained. Is there anything more current you would recommend?


Mobx. It's not small, but it is easier than redux.


MobX is similarly mature, easily as powerful, yet much simpler / easier to reason about.


Agreed. Or vue.

All in all, reactive programming is easier, because it doesn't require you to make all the plumping to get running water.


Yes, except when redux came out, nobody could know since there was so many competitors.

It tooks years to sort that out.

Yes except we spent years having so many projects crashing under the complexity of using a store, most juniors being unable to produce decent code and managing the numerous layers of indirection.

Yes except we learned later, hey, a store is not required for most projects. You totally should use react without a store for your little service. But we won't give you a best practice to do so.

Yes except for a single little incremental counter in redux, tutorials invite your to create a store, an action factory, and action dispatcher, an adapter to connect them, and then use a provider to pass the store. To escape this madness, you may use, yet another abstraction du jour, or just write hundreds of lines of boiler plate. Or do it another way, hoping it's alright, and creating yet another differently styled code base.

Yes except mobx or vue are way easier to use. Oh but they are yet another tool to learn and manage.


So you suggest people don't innovate? I think what you say about redux is FUD. Sure, a bunch of libraries came out at the same time as it trying to solve the same issue. Redux happened to get the most traction and as such most people use it. It works fine. Approaches to it have changed as people have found issues with approaches - now people are fine with being chill and using internal state for some things, and the redux store where it makes sense. It isn't as bad as you make everything seem. Hooks are a novel way of sharing logic between components, and people are trying them because it seems like a cool idea - if it works it will stick.


Here it is, the angry JS dev saying I don't see things clearly and that the current situation in fine. Hello my friend. We meet again, today. We'll meet again tomorrow. Then next year.

Rince and repeat, I said.


Well, it's not an easy problem. I've been working on user interfaces for around 14 years, in several languages and platforms.

React is the first time I feel like we've actually hit something good. It's still not fully there, but it's the closest it has felt to that so far.

State management is messy, that's clearly an area where the problem still hasn't been solved. The main issue is that state means too much really. There's your (typically) main source of truth: the database. Then your server may maintain some state (although nowadays this is not common, thankfully). Then there's the frontend app state, which loads from the server/db, and may or may not staty up to date.

On top of this you have temporary frontend state (unsaved changes), and finally pure UI state (hovering things, "show more"... )

Managing all of this is extremely difficult. Graphql with the appropriate frontend library for example is revolutionary in some ways, but a major difference in backend architecture, which isn't easy to reason about (fine grained entitlements, mutations & side effects such as notification emails, report generation.. are not easy)

Anyway, my point is that I understand your frustration with how everything's outdated too quickly, and how in the past we had stable frameworks or languages. But I feel the reason is that we are actually in a transition period, and making a lot of progress really fast. Hopefully the outcome of these messy years is a Standard™ way to define a UI, it's state, and it's interactions with an API, in an e2e type-safe, testable manner. One can dream right?


We agree on the fact it's a hard problem and i don't label react as a bad product. Plus, it's a necessary one given the way the internet is evolving.


> It tooks years to sort that out.

Yep, maturing takes time - duh.


Maturing is good. Marketting beta products as solutions to the problems you have right now isn't.

This problem doesn't exist in the PHP, Python or Ruby communities.

We get incremental improvements, sometimes a few innovative contenders show up, we evaluate it, then a winner appear, generally under a year.

With JS it's:

- a new wild pokemon appears !

- quick, everybody make 200 new pokeball models and throw them at it

- spend 3 years sorting said pokeballs, while the pokemon disapears


> quick, everybody make 200 new pokeball models and throw them at it

I mean, it's not like this is forced upon you. You're free to continue to use Backbone or Knockout or Ember if you want to. Angular 1.x has been replaced with Angular 2 (and I think the Angular team was rightly criticised by how they managed this transition), but most other popular frameworks are still around.

Of course, for new projects you may want to pick a newer framework if you believe it offers significant improvements over what came before. For me, React was clearly in this category. And yes, there are lot of people who chase the latest shiny thing, even if it is new and unproven. But that doesn't mean there aren't also stable, proven stacks.


Nobody lives in a vacuum, and there are many reasons for not being able to choose the techs you will have to work with.

In fact, the main reason is that there is no current other client side web language than Javascript.

We don't use JS because it's great. It's a badly designed languages, and half of the stack we use are hacks to make it usable. We use it because there is nothing else.


You’re comparing entire languages to libraries made for a specific task within one language.


No, I compare language ecosystems, and cultures.


"Every single time somethings come up, in the JS word, they try to repeat one of the mistakes from the past and completely ignore everyone else solutions and experience."

I agree with the sentiment so just out of curiosity: what are these existing solutions that people should look at and take lessons from?


A few on the top of my head:

- Clear best pratices provided with tools. Do you know how to code a react project ? The person next to you has a different answer.

- Reference implementations. We heard about the Flux pattern and the idea of stores for a long time before understanding concretly what it meant. And then pouf, 200 store and routing implementations differing in opinion, and no voice or advice about what to use and how.

- Integrations layers. Everything is a lib gets old really fast. And "look how redux is simple" as well. Assembly is simple but I don't code my website with it.

- Deprecation policies and clear migration path. Webpack was weird, quirky and badly documented. But the worst thing was that once you figured out something that works, a new version broke compat.

- Avoid dependancy creep. When a dependancy has more package metadata than code, there is a problem.

- Don't encourage a cowboy culture. It's great anybody can start coding in JS in an afternoon. It's not great that after a month the same person market his lib like it's the best thing ever and production ready.

- Documentation that makes sense. People explaining a new tool by showing you something webpack based or in in jsfiddle is not doing his job properly.

- Api best practices and design patterns have existed for a long time. Use them. KISS. DRY. Don't break user land. Principle of least surprise.

- Don't rename things that have had conventional names for decades. "Flux is a not MVC" is a lie. It's mono-directional, it encourages single instance immutable model. But it's still MVC. Most people looked for very complicated answer to what is flux because of that. And who name a lifecycle method "componentWillUpdate()" ? "onUpdate()" and "onPostUpdate()" are the legacy jargon. And then "UNSAFE_componentWillUpdate()" ? Really ? Like really ? Never heard of warnings ? Using the JS className instead of the HTML class in JSX is also still amazing me, not in a good way.

- Don't give the same name to 2 different products. Looking at you angular. Also SemVer is not a bad word, I and don't want your weird release name. That double the suff I potentially could google for.

- Conventions are nice. They should not replace configuration, but they complement it very well.

- There is such a thing as a "typical use case" for many tools. You should design for that, and if it's not possible for you, build a layer of top for that. Building facebook is not "a typical use case". Passing data from a children to a parent after a click and a preventDefault is. Why is the later so damn verbose ? Why did have I to google it ?

- Scaling down is as important as scaling up. Make simple things easy and complicated things possible, not the contrary.

- New is not always better, sorry barney stinson. Satisfying users with working software is. If you make or use something new, you should evaluate the cost for you and others before going "oh shiny !".

There are many others, but I don't feel happy writting this, so I'm going to stop.


I mean frankly going back to other forms of development (e.g. native apps) which seem to still be at the "jQuery" level of state management and UI mutating, I am glad there has been innovation in the JS space.


That's true. It's certainly better to explore than to pretend that the first solution you come up with is somehow the best.


They haven't copied om next (fulcro makes it easier) https://www.youtube.com/watch?v=ByNs9TG30E8 #graphs-not-trees


Hey Diego!

Nice idea and examples! Let me know if I'm wrong, but the main difference between `createContainer` and a `React.createContext` call is that you memoize inputs, correct?

The way this interacts with Hooks to enable state sharing is pretty cool. Hopefully someone builds a complete i18n lib on top of something like this.


Also check out react-atom: https://github.com/derrickbeining/react-atom

I stumbled across it earlier this week and it's a joy to use so far. The api is inspired by clojure atoms, and typescript inference works great out of the box.


Thank you, this looks really interesting. I've enjoyed using reagent and re-frame on personal projects but ClojureScript was too much for my team to swallow.


Honestly I think I'm done with state management libraries. There's no longer anything I can't write cleanly with React.Context and hooks. The overhead of adding an dependency is no longer worth it.


> The overhead of adding an dependency is no longer worth it.

But asynchronous effects! And their orchestration with powerful tools such as generators or observables! Isn't it worth a dependency or three?


> a dependency or three or three hundred? FTFY


That's great for simple stuff, but without implementing some extremely hairy bitmasking logic via the callback to createContext, if you have any kind of complex state, how do you prevent rerenders of vast chunks of the UI, and the performance hits that causes? Context is really nice, and works fantastically for simple stuff where it's fine to just redraw the world (authorization or theming for example), but it's not a replacement at this time


How does using a state management library prevent rerender of vast chunks of UI? Isn't that the responsibility of the react's reconciliation algorithm? Is there any way of fine-tuning the rendering, purely from a state-management perspective (I know about Pure Components and shouldComponentUpdate lifecycle hooks)? I am genuinely curious.


In the case of React-Redux, we do a _lot_ of work to check the derived values you've extracted from the Redux store, and prevent your own component from re-rendering unless those values have changed.

See my post "The History and Implementation of React-Redux" for more details:

https://blog.isquaredsoftware.com/2018/11/react-redux-histor...


Somehow the codebase I recently took over has managed to mess this up. Every time any part of the state tree anywhere changes, the whole app re-renders.

I’m trying to track down why this is an issue, but so far I haven’t had much luck. Do you know of any tools that can assist with this specific kind of debugging?


Context change -> Component rerenders. Easily fixed by implementing componentShouldUpdate for the critical parts of the codebase. But the author is right, it can be an issue


It isn't easily fixed that way: Context's single blob of state is difficult to optimise if you try to use it in the same way as (eg) Redux. shouldComponentUpdate can sorta work, but you need to do quite a bit of faffing with HoCs because the propagation from provider -> consumer doesn't use SCU (and PureComponent/memo are often useless with context due to shallow comparisons). Note, just to be clear, I'm talking something along the lines of a collection of objects (a Todo app would be an example), something with completely flat state is very simple and Context is ideal in that case. It can be approached by using HoCs that do the comparison before rerendering, but then that's react-redux' connect function


I'm not sure I understand. I don't use the Context API any differently than I would a Redux store.


That might not be an issue at all, often redux can add unneeded complexity, but the Context API isn't a direct replacement as things stand. It's not great, performance-wise, for dealing with collections of stuff. I like it a lot, but it's a blunt tool (which is totally fine for many things!)


To be fair, this library is literally the same as just using context and hooks. Look at the lib's implementation:

https://github.com/diegohaz/constate/blob/master/src/index.t...


> The overhead of adding an dependency is no longer worth it.

Wait until you start seeing components that depends on various trivial libraries (hook functions) for state management. My bet is on setInterval to be a very common one.


Given that the implementation of setInterval is far from trivial[1], that is likely correct.

1:https://overreacted.io/making-setinterval-declarative-with-r...


We must disagree on what is considered trivial or complex enough to warrant a 3rd party dependency and the complications that comes with it.


Is there a way to use the React hook stuff without having to put the backend logic _inside_ of your UI generation code?

It really feels like if there was some way to put this really dirt-simple counter code into a plain-Jane Javascript object then a lot of this hand-wringing about state management in React goes away (contexts are an interesting little solution to things though)


This article is almost four years old but I still refer to it all the time: https://medium.com/@dan_abramov/smart-and-dumb-components-7c...

What I tell my students is that you want to think of your app in three layers -- the data layer (Redux/data fetching), the glue layer (@connected components, useEffect hooks), and the presentational layer. The representational layer should be just your rendering logic and the data layer should deal with fetching/storing/updating data. The two should never interface directly, only through your container "glue" components.


The issue if I understand your comment correctly is that you want to keep the ui state in sync with the logical state, so you can't just keep it in an object somewhere, since that wouldn't keep up with react's rendering?

I do think you can have a hook return a javascript object though for the normal reasons of encapsulation and such.


Sounds like you are looking for something like MobX? http://mobx.js.org


There's also Redux, which is harder to wrap your head around starting out, but has great edge case debugging capabilities (you can "time travel" forward and back with changes) and really efficient-and-seamless cached derived data with selectors.


Rematch[1] makes it really easy to get started with Redux.

1: https://github.com/rematch/rematch



Mobx supports "time travel" as easily as Redux. It also works with redux devtools, and requires less boilerplate.


I've been thinking about it some. The pattern seems easy enough to entirely split view functions from hook logic. Using the usual counter example:

    export function counterView (count) {
      return <span>{ count }</span>
    }

    export function Counter () {
      const [count, setCount] = useState(0)
      // … useEffect to setInterval count or whatever
      return counterView(count)
    }
At this point, though the question becomes is this pattern buying you anything? Pure view functions should be easier to test, but React testing library's act() tool looks like it isn't that much more painful to just test the full component. Similarly, pure view functions are potentially easier to reuse with different "component logic", but I'm unsure how often that would be the case.

It's maybe a bit clearer as a pattern where the "stateful" and "stateless" stuff may be than mixing the two, and certainly seems like it might help make it easier to follow the "Hook rules" (most of your loops and conditionals should be inside the "pure" function). But it's potentially also just a source for extra noise/boilerplate.

Like I said, I'm still thinking about it, and something I expect to explore in an actual codebase at some point.


I think this TodoMVC might be structured the way you described: https://codesandbox.io/s/github/solkimicreb/react-easy-state... It has a separate, clean JS store object. (Note: I am the author of the underlying state management lib.)


I dont get hooks. How is this simpler than something like this?

https://jsfiddle.net/xv7y9eoz/


I don't buy the way things are done in this library (constate). I wouldn't import a library just for doing this. It is not very hard to achieve the same with plain `useContext` and `useState` directly.

That being said, I don't agree that hooks are futile, because, using hooks, your state logic can be reused. For example, if your app uses 10 independent counters (with different render logic and different styling), hooks are definitely useful to reuse the state logic.


Just because the example people use (incrementing a counter) is simple and can be solved very simply, it doesn't mean that these simple solutions will work well for more complex issues. Of course most people are not building apps that just increment a counter. Hooks are for much more than that.


That only uses local state. With constate you can access the state from anywhere without prop drilling.


Seems to solve a similar issue as https://github.com/jamiebuilds/unstated


Both solve state management issues, but really different goals.

Unstated has state containers as separate entities. This one is about seamlessly switching from a local useState hook to a context-based one with minimal code changes.

I’m not convinced it’s worth the hassle vs just creating your own contexts though. Here is the full source: https://github.com/diegohaz/constate/blob/master/src/index.t...


> I’m not convinced it’s worth the hassle vs just creating your own contexts though

This is what I was thinking when running through the source. The problem to solve, I think, is to get rid of the providers. Or rather, make it less jsxy. For application-level contexts, you can easily have 10+ providers at the root of the application. You could make them HOCs, but that's still super ugly. Hooks made the consumer side a lot cleaner, now it's time to do something about the provider.


I guess I'm still in a minority who likes Redux.


Nope. plenty do. I do. granted its verbose, but it gets the job done nicely. easy to follow, plain functions all the way down.


And what about those `reselect` wires? And that `connect` and `mapStoP / mapDtoP` stuff? Doesn't that "bother" you? Or you just mentally "pack" it into the verbosity basket and ignore it?


`reselect` is an option that you don't have to use.

The mapStoP and mapDtoP are just plain functions that do what they say on the tin. Easy to read/write once you understand the pattern.

The connect HOC like all HOCs is a bit of a smell, but easy enough to use without understanding much about HOCs.

Likely soon hook replacements for connect will make all that "verbosity" even simpler and easier to work with.

For instance, useMapState and useActionCreators from this proposal for redux hooks: https://github.com/epeli/redux-hooks


> `reselect` is an option that you don't have to use.

Of course.

It just happens that for my real life needs I've needed state that was derived from redux state: filtering, selections, pagination, normalizing / denormalizing state, relations between entities (classic library data model problem) etc.

So I kinda can't "imagine" any "serious" work with redux without helpers like reselect etc.

> Easy to read/write once you understand the pattern.

Sure, there is nothing "complex" about any of that.

And I am genuinely asking how people deal with that.

It is just way too verbose for me. So I am wondering, how other people approach it.

Close their eyes and plow through? Don't really care?


Genuinely asked: is there a viable alternative? If so, what is it?


IMO mobx and mobx-react-lite (hooks) are brutally (!) effective to manage any globalish state.


Maintaining state is an easily solved problem severely over engineered by frameworks. The central issue why this becomes so complicated is because developers who aren't comfortable writing original code would rather deal with an ocean of configurations than a few architectural decisions.

That is problematic because configurations are settings not decisions, which means you need work arounds for edge cases and work arounds for the work arounds. If instead you treat everything as a requirement instead of anything as an edge case then you are forced to make decisions and write the corresponding code. New requirements may necessitate refactoring of current decisions down the road, but the code stays clean, small, and deliberate.

At the most simple you only need three things: a central consolidated point of settings (object), storage (localStorage), and interactions isolated to their respective purpose (handlers).


This is such a gross oversimplification of the problem I can't help but doubt you've ever actually written a complicated frontend application using today's modern frameworks.

I have explored this space from nearly every angle. I've tried a multitude of languages and technologies, and there is no place where this has been solved completely and there are always drawbacks to any solution. But that doesn't mean those solutions just exist because their authors were bored. They exist because they actually solve a problem.

I would very much like to see you write a large react application using only the concepts you mentioned. How are you going to deal with side effects? You realize you can't just fire those off willy nilly, right? How are you going to pass information upwards or across the component hierarchy? Callbacks? Have fun with writing that and reasoning about it afterwards.

People love to bash on the JavaScript ecosystem and rarely do I see anyone praising it as it deserves. There are some incredibly ingenious solutions and architectures that have been invented in that space, and anyone who isn't just a troll trying to fit in with the other cool kids and has actually tried to write a complicated frontend application today appreciates the hard work that is happening there, and understands why new solutions keep being invented.


tldr; everything is too hard

I have been writing code for more than 20 years and writing JavaScript for more than 11 years full time. The UI, accessibility included, is not the challenge. I have been doing this long enough at enough employers to watch people fail the same ways over and over and consistently qualify bad decisions with the same repeated excuses.

> This is such a gross oversimplification of the problem

It is not an oversimplification if proven in practice more than once.

> rarely do I see anyone praising it as it deserves

I love JavaScript/TypeScript. Some of the complaints against JS are absolutely justified and some aren't. The unjustified complaints are typically from insecure developers of other languages that loathe the completely foreign expressiveness and flexibility allowed by JS. The mountains of code some developers require to perform trivial tasks is absolutely worthy of ridicule though.


How many times does the React community plan on skinning the same cat?


It's a mean cat. In the last few years I've ended up going through cycles of doing a react project, moving on for some months then another react project. Each time I've come back I've been impressed with the changes and they've often improved parts of react I'd previously found wanting.


[flagged]


Ha, total opposite, our small company has near 100% conversion from greenfield to ongoing maintenance contracts, some days this is a point of pride AND a cross to bear :)

It can take minutes to remember/read my wiki notes on a project if I haven't seen the code for months but the alternative would be to stop trying/learning new things on new projects.


Of course not. It's not a binary choice.


The nicer the code becomes, the easier these projects will be to support. Frankly the shitty old rails code is more of a thorn in the side of the current place of work. With TypeScript, refactoring is piss easy, too.


Typescript has overall been a godsend on larger frontend projects. I'm replying more to add to the public thought and support of it.


Do you really have to?


How dare people try new things. We should have stuck with COBOL. Or bashing rocks together to make fire.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: