Hacker News new | past | comments | ask | show | jobs | submit login
Recoil – A state management library for React (recoiljs.org)
109 points by dar8919 on May 14, 2020 | hide | past | favorite | 85 comments



I'm someone who through sheer coincidence (the right projects at the right time) happened/lucked into developing a React state management solution (https://kea.js.org). I've probably sunk hundreds of hours into this project by now.

5 hours before this HN post I asked for comparisons with different site management libraries on Github here: https://github.com/keajs/kea/issues/106

React state management is indeed a mess, meaning there are so many options to choose from. Unfortunately, even for me, a library author, it's hard to evaluate all of them properly. It would take just too much time and you never know where the real problems in any framework lay before you get neck-deep in code. And then you're too far to switch to another framework anyway.

Someone should really map out this landscape in more depth than just an awesome list. I hope the issue linked above can spark some discussion.

In any case, I personally think Kea is a very good solution for React state management, yet obviously I'm biased. Shrug.

And, to keep it on the topic, Recoil seems very low level and I think you need to invent a lot of things yourself to get a full app out of it. Cool to see people trying stuff though :).

And it's from Facebook so who knows, just by proxy it might become the default "atom" and someone will build molecules out of them...


Keajs has been amazing I upgraded from 0.28 to 2.0 yesterday and had 1 small issue, had to delete [actions.] from the reducers.

I use to use just Redux and it was such a pain.

I know you have added even more features in v2 that I will need to take advantage of.

If you need to add examples https://versoly.com/ is what I built on top of keajs.

Has about 8 logic stores that have 100s of actions, thunks, reducers and some interesting selectors.


Hey! Thanks for the comment and that's really cool! Your product looks really slick as well!

Would you mind posting in this "who's using kea" issue about versoly as well?

https://github.com/keajs/kea/issues/35


Never made any OpenSource lib, but I always had that gut feeling you are talking about here:

"Unfortunately, even for me, a library author, it's hard to evaluate all of them properly. It would take just too much time and you never know where the real problems in any framework lay before you get neck-deep in code. And then you're too far to switch to another framework anyway."

Putting that aside, to me RxJS and especially Cycle.js are "1 abstraction level above" React & friends. (Cycle can be used via React.)

What do you think about these?


I'm not seeing how this works with more complex state flows. Doesn't seem much better than useReducer.

Frankly, for state management I still haven't found anything that beats Redux on its own without thunks or sagas or any of that bullshit. This is despite doing my level best to see if useReducer on its own would be sufficient. It's not.

Thunks are unmaintainable. Sagas are put together with bailing twine and rely too much on generators for my taste. (The source code is smart but it's also wildly complex.) All of it is castles in the sky for no good reason. You don't need any of that anyway.


Well, I know that on one tool we saw a 20x or so speedup compared to using Redux. This is because Redux is O(n) in that it has to ask each connected component whether it needs to re-render, whereas we can be O(1).

useReducer is equivalent to useState in that it works on a particular component and all of its descendants, rather than being orthogonal to the React tree.

I think if you can model something with pure functions, you should. That's the approach we try to take for asynchronous processes: just a pure function that you happen to evaluate on some other process. This obviates the need for things like sagas. So I agree with you there I guess.

If you post an example of what you don't think it could handle I will tell you how we would handle it.


I've never had my bottlenecks end up being because of Redux, but that could just be me.

I'd love to take a look at a larger project using Recoil though, just to get a sense for how it looks with a relatively complex state setup. My first impression is that it would get messy pretty quick, but I've been wrong many times before :)

Also, I'm not trying to shit all over your project, congrats on rethinking state management. Regardless of how I feel about your library, that's still awesome.


The app in question had thousands of connected components, so that was a huge bottleneck for them. For many apps it doesn't matter.

The app that Recoil was originally extracted from has an extremely complex set of state and interdependent derived processes -- also heavily hooked into and modified by third-party plugins. This type of complexity is exactly what Recoil was designed to handle.

Thanks for your kind words.


That's awesome. Best of luck going forward!


You can hoist useReducer out to context and end up with essentially a very lightweight redux. I’ve had some success with that in smaller apps. For something large or long term I’d probably avoid that approach though.


>Well, I know that on one tool we saw a 20x or so speedup compared to using Redux. This is because Redux is O(n) in that it has to ask each connected component whether it needs to re-render, whereas we can be O(1).

Don't I get the same perf with selectors?


I could be mistaken but I think that you still have to do a shallow compare on the connected props of every component.


Nah, you're completely correct


> I still haven't found anything that beats Redux on its own without thunks or sagas

There was a period in the early days of Redux where MobX [0] (and, perhaps to a lesser extend, MobX State Tree [1]) was the main competitor to Redux that I used to hear about. They both seem to be actively developed, but I don't hear so much about them any more. Have you ever look at either of them?

BTW I am 100% with you on Sagas.

[0] MobX https://mobx.js.org/README.html#introduction [1] MobX State Tree: https://mobx-state-tree.js.org/intro/philosophy


At least in the company where I worked in, everyone agreed that Mobx was the better option between it and Redux. It's so much more intuitive to grasp and using simple functions and objects much easier, than having to grok weird async-flows and cumbersome action-generators and whatnot.

Only problem so far, or inconvenience maybe, is the sometimes complicated ways you come to intertwine stores which have access to different parts of the fetched API data. So if you need data Y stored in store X inside store Z, you have to inject store X as dependency to store Z. Getting too carried way with these injections can make your whole state quite messy. Or loading too much logic into one store.

Probably that's just an architectural issue, that can be remedied with enough thought put into it, but I haven't at least found a clear pattern that would make it simple to decide when to break up stores, or how to refactor them.

Also you have be aware of the complexities of observables which are a bit magical. Knowing when observables don't update and how to observe in general is very much a necessity (unless you want interesting bugs).

I have been curious to try out Mobx State Tree but alas haven't had time.


MobX State Tree advocate here. We have a VERY complex state that needs to be synced with the server and persisted locally and nothing fulfilled our needs like MST.

Also, sign me up to the Sagas-are-bullshit club.


Mobx itself is insufficient? What does mobx-state-tree provide that mobx does not? The README doesn't convey anything meaningful.


Well, you know how MobX itself is very unopinionated? MST is very structured and opinionated. It's also immutable which means you get snapshots of every mutation to the tree which is very useful in almost all cases.


Mobx itself is fine for most usecases I think. I've used it on 4-5 projects now and it's been working very well in each.


Zustand works for us without much complexity. Its fast and quite easy to use

https://github.com/react-spring/zustand/


I still use mobx for almost everything. Sometimes without React (like a dmx light controller and a midi controller for guitar effects).


Having worked many years on mid to large scale React apps, I must say: In most cases component state (or useSate) and context works just fine.

Why is everyone so eager to add third party state libraries from the getgo? In most projects, Redux doesn't add net value.


Coming from Clojurescript and using libraries like Re-Frame it feels much more natural to me to have a central store for most state. Sure I also use component state but I leave that for very simple cases. It seems like Context could also provide something like this but I'm not sure how it handles re-renders. If one part of my Context state is modified, do all Consumers re-render? Or just the ones that would be affected?


All of them. I’d say that’s the biggest problem recoil is addressing.


I've been working with React since 2014 and I've never had a good experience with an app that relied entirely on component state. To each their own, perhaps you've discovered something I haven't.

For me, Redux works very well with how I think about control flow. That said, my brain has been ruined by a youth misspent in fp land, so my preferences should be taken with a liberal application of salt.


For complex state flows we use Kea JS. https://kea.js.org/

The library has been around for a while and reduces a lot of boilerplate.

Is also used by some quite big names.


One viable alternative for thunks and sagas that I came up with is using async functions and explicitly getting/setting state: https://github.com/riptano/statium#viewcontroller

Still a long list of chores to improve API, tooling, etc but very performant and battle tested.


If that works with your model of the world that's great. Wouldn't kick it out of bed for farting.

For me, the easiest way to handle async and effects in Redux is to write custom middleware for each context/entity. That's what it's for. It makes it possible to have a real message based architecture without hacks and (extra) libraries.


> If that works with your model of the world that's great. Wouldn't kick it out of bed for farting.

Oh I see, entity naming could also be improved. ;)

> For me, the easiest way to handle async and effects in Redux is to write custom middleware for each context/entity.

Right, and you still have to deal with Redux to get there. Not a preferred choice for many people.

> have a real message based architecture

What is a real message based architecture, in your terms? And why is it so important? Genuine question.

> without hacks and (extra) libraries.

Extra libraries only if you base on Redux and go from there. It's almost as if Redux itself wasn't an extra library...


Redux is fantastic and it's quite a simple library. The rest is what gets you into trouble.

Hardly anybody has a problem with redux until they have to deal with async actions but all the tutorials use thunks or sagas when the correct way to handle that stuff is to write middlware.

By real messaging I mean CQRS with event mapping/filtering/normalization.


> Redux is fantastic and it's quite a simple library.

... and it's an extra library. You can do without it in React, you know.

> The rest is what gets you into trouble.

Exactly. My biggest beef with Redux (and React, too) has always been that it makes much pomp about things that are relatively easy to solve, like state computation (or rendering). The actually hard things are left unsolved to a various degree, and that is what devs struggle with the most. Things like: how do I take the business logic best expressed in imperative statements (get foo, if it equals to bar, do that, if not, do something else), and express it in React component state transitions, with loading state indication, error handling, and conditional branching?

The most contrived React code I've ever seen was dealing exactly with that: fitting a finite state machine into a React component, which Redux is essentially zero help with. Why do I need Redux if it doesn't solve the hard problems for me?

> Hardly anybody has a problem with redux until they have to deal with async actions

Srsly? So tons of boilerplate, action at a distance as a blessed pattern, reliance on globals, contrived testing, and perf issues are not problems at all?

> the correct way to handle that stuff is to write middlware.

There are as many correct ways to handle that stuff as there are developers. Some ways are just easier to read and follow (also to test, maintain, etc etc).

> By real messaging I mean CQRS with event mapping/filtering/normalization.

Thanks for the explanation. You forgot to answer the second part of my question, which is: why this is important for a front end application. Again no sarcasm, just genuine interest.


I didn’t forget to answer anything, you are just treating this like a debate. While I understand why, it would do you some good to be more humble. You’re pretty junior and you still have a lot to learn.


Agreed. But Redux loop is nice. Kinda like reframe for JavaScript.


Mind describing why you dislike thunks


A few big problems:

* They're a pain in the ass to maintain once you have more than a few async calls

* They are a poor abstraction because async messages often need to do a lot more than just call/response/error. Sometimes you want stuff like backpressure, throttling, etc. and that's a lot messier to model with a thunk

* There's no real need for a library for thunks in the first case, you can mimic the same functionality with the same amount of code by putting async calls in mapDispatch

If I'm working on a small project then shoving async in mapDispatch can work. Most of the time though the best approach is to handle async through a custom middleware pipeline because let's be real–if your project is simple enough that thunks are fine, you probably don't need to be doing it in React.


Here's an awesome video from the library author explaining a bit more about Recoil from today's React Europe live stream if anyone is interested: https://www.youtube.com/watch?v=_ISAA_Jt9kI.


I'm happy that we are moving away from the redux-like standard. I've tried the API in a dumb example and it looks clean and easy. https://codesandbox.io/s/three-buttons-example-with-recoil-q...

Still don't like the extra naming that you have to think of for every atom/value. Or also the key: prop that you have to define for every atom. It seems redundant for me.

And also, as a bundle-phobia guy I am. I think 22kb as extra library is ok but it could be better.

Liking a lot Recoil as solution for the three-button problem. I still think that the patch pattern I use with dop is better. In terms of simplicity and extra KB. This is the same example https://codesandbox.io/s/react-dop-nhj0j?file=/store.js

But I guess I say that because I'm the creator of dop. So I'm so open to critics :)


What is it about React that state management is such a such a hassle? This is not the case in other frameworks.

Consider iOS for example. Just add fields in App and you're done: https://developer.apple.com/documentation/swift/maintaining_...

Similarly in ASP.NET: https://docs.microsoft.com/en-us/previous-versions/aspnet/75...

Same in JSP: https://javabeginnerstutorial.com/jsp-tutorial/state-managem...

But in React it is such a huge issue, and there are multiple competing solutions. There must be something wrong with the design of React that is causing this.


Its a huge issue in all of those also. It just presents itself differently. JSP -- every click has to post or query param change re-renders the page. This gets hugely cumbersum with tree stateor anything other than basic switches. In a lot of ways its the one way data flow we all want in react. For a lot of pages its easy. Same with React, However complex state is so difficult to do in JSP you really can't write the apps we write today.

I would say React's state management is its great success, rather than its `functionalness` or its reactivity. No other UI framework (QT|WinForms|MOTIF|GTK) really ever handled state well.


Don't have experience with ASP or JSP, but managing state in React is vastly superior than iOS. Having a defined place to keep your data, and your view automatically refresh when it changes is the gold standard in user interface design. iOS does not easily have this kind of functionality, forcing people to use all sorts of messy techniques that are hard to follow and maintain.

The main "Apple approved" tools for user interface and state management are CoreData and UIKit. Both are imperative, mutable, and overall difficult to work with. This might be subjective, but I always notice just how poorly Apple's own apps work. There is always some button that is stuck in the wrong state, some feature that doesn't do what its supposed to, and some broken animation when views reload (I'm looking at you Podcast app). I can't know why, but my hunch is that Apple engineers are forced to use unwieldy patterns and technologies like CoreData, MVC, and UIKit, and are drowning in a sea of unmaintainable code.

As a response to the inadequate tools Apple provides, big industry leaders have stepped up and rolled their own declarative solutions. IGListKit from Instagram is a prime example of a declarative user interface solution thats vastly superior to Apple's. Going even further, Facebook basically ported React into iOS with ComponentKit.

It took Apple a good decade, but they are finally giving us these types of tools as well with SwiftUI. However, judging from its low adoption, so far they have not managed to fully get things right.


> Having a defined place to keep your data, and your view automatically refresh when it changes is the gold standard in user interface design.

First, in most frameworks there is only one place to keep your data. This can be your App object or session state.

Having view automatically refresh is overrated. Given the amount of work you have to do (actions, reducers, dispatch and whatnot) it is easier to just update the view yourself.


You don't need any of that if you just use React. All you need is `setState()`. All of those extra things you are thinking about are part of Redux, not React. I love Redux, but even just pure React is a huge improvement over UIKit.

UIKit kind of reminds me of Backbone.js - it has some infrastructure but its up to you to build a lot of the tools yourself. You might prefer that personally, but I think the movement of the frontend community from Backbone to React shows that most developers prefer a more full feature solution.


> You don't need any of that if you just use React.

You do need it if you use ReactRouter. Otherwise your state will be spread (or duplicated even) across multiple components, and it will be a nightmare to deal with.


Whats stopping you from just importing a plain javascript module that has mutable data on all your screens? You can even write it to local storage on every update to preserve it across app refreshes.

Thats essentially what you have in iOS with your AppState example. There is no need to use a Reactive Store if you just want to share some state between screens.


React is not a complete framework more of a templating engine and nothing more, it allows you to use any state management you want. It also makes it easy to add to existing projects without requiring rewrite of everything from the ground up.


The downside of this is that it's often the complete wild west when building and doing maintenance. It's not uncommon to end up with wildly different state management solutions in every project because the prevailing winds change so frequently. This can have a big effect on productivity and morale. It's one of the main reasons I'm trying to get out of programming for my profession. I'm so fucking tired of having to chase the flavor of the month.


> What is it about React that state management is such a such a hassle?

I'm not sure it's a hassle, just different schools of thought.

> This is not the case in other frameworks.

None of the examples you mention are reactive AFAIK. The point of React is that the UI is a function of the state. How exactly that is accomplished is where those different schools of thought come into the picture.


You can use React with traditional MVC to get the benefits you mention and still avoid state management hassle.

Here's one such lib: https://www.npmjs.com/package/mvc-router-spa


React has fantastic tools for state management built-in. The difficulty is a particular flavor of state management: data that lives in the "global" scope but can be used locally in components in a reactive fashion. We have some good tools for doing this but they all have drawbacks, hence the experimentation.

FWIW I've never seen a really great solution for this in any JavaScript framework. I read it as a sign of React's maturity that this is a focus.


Perhaps it's due to React's architecture of one way data flow?


Check out easy-peasy. Ive used plain ol redux, mobx, and xstate, but easy-peasy is the fricken best. It's basically a redux wrapper that combines redux, thunks, reselect, redux logger, immer, and more into a super intuitive interface. The typescript support is super good and interacting with the API feels very modern and hook-driven.

https://github.com/ctrlplusb/easy-peasy


If you want even cooler try overmind:

https://overmindjs.org

But Recoil is slightly different, designed to be “component first” essentially. Not global. It’s how my own personal mobx based library works. But this one likely has concurrent mode baked in.


Sounds a lot like re-frame[1] (which I believe predates Redux), they even call the state "atoms."

[1] https://github.com/Day8/re-frame


The term 'atom' is a clojure-ism, that's where both I and reframe get it from.


Re-frame's atoms are actually Reagent's 'ratoms' ('reactive atoms'). They're built on Clojure atoms, but can reactively prompt a re-render of any component that depends on them when the content of the ratom changes.

Re-frame wraps Reagent, and introduces the concept of "subscriptions". A subscription either returns the content of an atom, or state derived from it, equivalent to Recoil's selectors. Re-frame also introduces the concept of a global application DB, a single atom that contains various pieces of state, such that you can develop your entire app around it.

I haven't tried Recoil, but I'll give it a shot on my next JS project - I tend to use ClojureScript for front-end precisely because I find the Reagent/Reframe approach simpler and more effective than any of the plain JS React approaches (for complex apps, anyway).


Lots of software has "atomic" concepts. Like SQL databases.


It would be useful to have a more direct comparison between this and Redux (assuming its not on the website and I missed it). In what use-cases is it preferable, in which would it not be a good fit? In the comments you mention O(1) vs O(n), diving more deeply into that would be helpful as well.


I’m working on a detailed comparison for the docs. Thanks for the feedback!


Seems like compared to Redux this would make server-side rendering very challenging. Each atom is a global singleton so I'm not sure how you could render individual requests.


It’s actually designed to make server rendering easy. We’ll add a guide about this eventually. Atoms aren't global singletons: their values are scoped to React trees.


Can you talk about SSR here just a little to give us an idea of how recoil makes it easy?


Well, I should say "doesn't make it harder". The `RecoilRoot` component accepts a prop called `initializeState` that lets you specify the state of all atoms in the initial render.


Shouldn't the app be allowed to "pull" the required state from the db, rather than having to "push" the initial state into the root of the app? It's not like we should dump the whole database into atoms just in case the app needs to look up one item, right?

I'm mostly curious how this might tie into a server-side DB. Recoil's API provides the fundamentals for a firebase-like persistence system that allows people to skip the complexity of GraphQL, and just use the type system provided by the language (flow/ts/reason).

In any case, congrats on launching such an elegant API. This is one of the nicest reactive systems I've seen for React :-D


We are planning in the next few weeks an overhauled persistence API. Among other things this will add the ability to provide a callback in `initializeState` rather than a value. However, this doesn't help if you need to do async work to retrieve the value. The way we generally think of SSR is that you get a single render pass and then you're done, no time for async work. So for hitting a database you want something like Relay that statically knows your data dependencies and can do a single request to initialize. It's true that there's some complexity there, but there are also great solutions to really hard problems. Recoil doesn't attempt to address that space.


How exactly does the default state work if that's the case? Is it just up to the user to treat the state as immutable and copy it rather than modifying it?


In a year people will rediscover MobX I guess...


Lol, MobX literally uses the concept of atoms under the hood.


Is there any support for changing multiple atoms at once / batch? This is where reducers shine to me - dispatching a single action from the ui in redux that multiple reducers can listen to and use to update their state.

Also is there any support for getting the values of atoms outside of a hook? For example from an async action that is not coupled to the render loop which also wants to know the current value.


If you update multiple atoms within a React batch, they will be updated at the same time, just as with React local state. You don't need to wrap the changes in anything to have them occur together.

In other words, this updates both of the atoms together:

  const [a, setA] = useRecoilState(atomA);
  const [b, setB] = useRecoilState(atomB);
  ...
  onClick={() => {
    setA(a => a + 1);
    setB(b => b + 1);
  }}

If the new values of multiple atoms are interdependent on each others' current values, it's possible to update multiple atoms together using the transactional/updater/function form, but we need to add a nicer interface for doing this. Coming soon! The nicer interface would probably look something like this:

  const update = useTransactionalUpdate([atomA, atomB]);
  ...
  onClick={() => {
    update(([a, b]) => [a + b, b - a]);
  }}
It's then easy to make a hook that provides a reducer-style interface over a set of atoms. But now, unlike with Redux or useReducer, each atom still has its own individual subscriptions!


It seems like something could be written around useRecoilCallback() to watch/get the current value of an atom outside of a React component. Does that sound right?


For a specific set of atoms you could subscribe to them with a component and then use an effect to send the values out. For all atoms you could use useTransactionObservation.


Seems to be roughly the same amount of boilerplate as useContext, so I don't fully understand the separate purpose for it?


Recoil allows you to create atoms and selectors in loops without having to add an entire new Context.Provider component to the root of the React component tree, which would obviously not be viable since the entire tree would need to be torn down each time.


This is the part that completely lost me. Why do you need an additional context for each item? Isn't one context holding all items enough?


If you have a lot of pieces of data in one React context, every component that uses any little piece of that data will re-render whenever any part of the data changes.


React has more intelligence built in than that. That's definitely not quite how it works.


React doesn't have to commit to the DOM if there are no changes, but it still has to render each component and compare the output. Even if the component is memoized, it still has to compare the props. This is fast enough most of the time but it can become a bottleneck in some cases.


Yes, and component memoization (and PureComponent and shouldComponentUpdate) only prevents re-rendering of the component's descendants. But every component that uses a context value will still re-render every time that context value changes, which could potentially be way too many.

For example, if every Cell component in a big Table component is using the same context value, they will all need to re-render when any cell value changes, regardless of whether the Cell component or some of its descendants are memoized. This is a common pattern in Redux, which solves this problem and will only re-render the Cells which are using cell values which have changed. Recoil would provide the same benefit.


Can anyone shed some light on if and how this might be used together with xstate (https://xstate.js.org/) which is based on the SCXML spec?


I'll experiment with this.


Anyone else see Svelte stores when they look at this?


Great work on this davidmccabe! I remember hearing about this just before I left FB and am so glad that it is open source now.


I wish he would have shown the heavy tree example in 11:55 without the library, seems cute and all but if you don't provide a comparison it's hard to take seriously.


Conceptually, this looks very similar to observables and RxJs. Should be interesting to explore further.


Ffff




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

Search: