Recoil: State Management for Today's React - Dave McCabe aka @mcc_abe at @ReactEurope 2020

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey folks thanks for tuning in I want to show you a thing that I made it came out of a tool that I work on at face book called comparison view which is a data analysis app for performance data so we collect data about performance profiles that kind of hang from client server we take a whole bunch of this stuff and aggregate it together and then comparison view is this app that lets you analyze that data and we face various challenges with it it's got a lot of interactivity where you're like editing one thing and another thing is sort of changing and sync with that it's got just a lot of different settings that then go into queries issued at runtime to different data sets which are then combined on the client it has sort of persistence so we heavily put state into the URL so that you can then share it with somebody else who can then see exactly the same thing that you're looking at and it's got a lot of functionality that's provided by partner teams and so there's sort of these third-party plugins that have to do a lot of stuff in the app and so because of those things we've always hit limits with existing solutions for state management whether it's just using built-in react state or something like Redux will hit like flexibility limits or performance limits or just things where we find that code is sort of bug prone the way that we would write in another library and so we've had to solve a lot of those things in a different way for this app and we've extracted that solution into a library which you can now use and it solves three basic issues the first is flexible shared state the ability to have different things that are in sync at different parts of the reactor e in a way that's really performant secondly derive data enquiries the ability to compute things based on changing state efficiently in a way that is very robust so that we can move quickly and not have bugs and then finally app wide state observation so for things like time travel debugging undo support persistence logging that kind of thing the ability to just observe everything that's happening in the app from some component and so we made this little library that does those things and we call it recoil it's very minimal it's just adding extra little ingredients to react to help with those things so that you can use just a little bit of recoil in your app when you run into limits with whatever you're currently using I'm just going to give you a really really quick tour of how it works so the first issue is shared state and I made this little demo app and it's just this drawing canvas prototyping app so you can have these things and sort of put labels in them resize them drag them around just have these shapes on this canvas okay and so I'm just gonna add a few of these right ok and I can just drag these around now the way that we would structure this the state for this app using just react state you know I'm sure you can already imagine this we're gonna have this app component at the top and in its state it's gonna have a list of item IDs so each of those items that we drag on to the canvas is gonna have an ID and we should have an array of those at the top level of the app and then once we have that array we're just going to iterate over it and we're gonna render out a canvas item component for each item that's in that state and then the canvas item we have some more react local state that has whatever that state is for that item so it's X Y position Heights and width layout what shape it is all that type of information now the reason that we've pushed that state down into the individual item is so that when we drag it is performant because if we had to re-render the entire app or some the entire canvas whenever we updated this component it would be much too slow to be interactive we want to just rerender the individual component anytime we change its State and not have to rerender all these other things that we see on the screen so we've pushed it down the state into that individual component and that works fine for the app that you see here but once we add some features then it's not going to work quite as well so here I've added this sidebar on the Left that has a list of items so you know the selected item you see when I move that information that's on that sidebar changes in sync with moving it and then also get this inspector on the right and that information changes in sync and I can move it over here and it'll move and that kind of thing right and I can set the background color so we'll just set that to a nice white color and once you have this situation then you've got a problem with the previous thing that that we saw because you've got your canvas component over here in this reactor II and it's got all those items that are on the canvas and over here you've got your list component and all the items that are over here on your list and you've got this corresponding components for a particular item like item number two here and their way apart miles apart in the reactor II and so the normal thing you want to do and react if they need to share state is to hoist it up to the common ancestor but you can't do that here because the common ancestor has too much stuff in it to rerender while you know while you're editing and moving that thing around you'll be much too slow to rerender everything up in that tree so you can't do that and the next thing you might try as well maybe we'll use react context and so context lets you have a provider up to the top of the tree and then these two consumers that are listening to it but nothing in between has to re-render it's like a direct thing from provider to consumer but that's not going to work in this case either and here's why we don't know how many of these items they're gonna be there's gonna be like one individual piece of state for each item that's on the canvas and so if say we had six of them in like in this picture we need six context providers one for each item because each one of them is going to need to update separately right in order to get that behavior where we don't have to rerender all the others and there's nothing wrong with having a lot of context providers I've seen an app that has over a thousand contacts providers it's not really a big deal the issue is when you don't know how many right if you need to insert a new one like when you drag a new item onto canvas if you have to insert a node at the top of your tree you've changed the shape of the tree so react has to actually unmount and remount that entire tree so that's not going to work and even if that was an issue you've introduced this in between the leaves of the tree and the root of the tree and it's kind of disadvantageous because it makes a lot harder to code split like what if the leaves are coming from plugins you'd have to know what plugins you're going to need all the way when you're rendering the top of the tree it would be much easier if those two things now the leaves could just cooperate with each other without involving their ancestor at all and so the way that we solve this type of problem with recoil is I think of it like taking this entire reactor II we're just gonna lie it down on a table okay so this is the reactor II and it's lying flat and then we are going to enter the third dimension and create another tree orthogonal to the react tree floating above it in space and we're gonna have a piece of state for each of these items that we had on the canvas floating above our react tree and the first piece of state when it changes we're gonna rerender the first item on the canvas the first time on the list and then the second third and so forth so each item is just going to each piece of state is going to have its own individual components rerender when that state changes okay that's the basic idea we need a name for these pieces of state we're gonna call them atoms so an atom is a changeable subscribable unit of state okay so let me just show you how this code works I'm gonna do the simple case first which is background color so here I've got this background color picker component and I've got the backdrop on the canvas when I change this background color I want for only those two components to re-render and not anything else that's in the tree so that I get this nice smooth change in background color that's a good color all right so this is the picture we're going to have this background color atom and then it's going to be connected to these two particular components which will rerender when it changes and nothing else in the tree is going to re-render so here's this app component that we had the four up at the top and we just need to make a very small change we're gonna add this recoil root component it's just a context everything that you see in recoil is going to be scoped to that you generally just want one in your app now let's look at the two individual components background color picker and canvas backdrop that we want to be in sync so currently each of them has their own component local state they have color set colors use state okay so currently they just a separate piece of state we want to make it so it's the same state between the two components so we just need to make a couple changes we're gonna import something called background color from another module into both of these so they have a reference to the same thing called background color and then where we have used state we're just going to change that to a new hook use recoil State and we're gonna pass in the background color which is this thing we imported from the other module once we've done this both of these components have local state that it's the same state okay and they're gonna be sharing their state with each other now you could think of it as if that was react local state but suppose the two components had rest to each other and whenever you called set color on one it would just sneak over and also call set color on the other all right so the semantics of batching and scheduling all that stuff works exactly the same as react local component state but it's shared so what is this background color thing that we imported from that other module so that's the atom that I was talking about earlier so we're just going to define that we he'll atom and then it needs a couple of options it needs a unique key and just like react State it needs a default value once we have that those the only changes we need to make so that this we get this nice synchronized thing between these two components okay so I'm going to now do the harder case where we have an indefinite number of atoms one for each item that's on the canvas which is the thing that you can't easily do with react context so you'll notice that each of these items has an ID we have that idea ray up at the top of the app and what we want is for a different basically one a different atom for each of these so let's just make a function that takes an ID and R it returns an atom you know so when we we call that function we'll have the ID of the item we want and we'll get the atom for that item okay so each item will have its own atom its own state so it just needs a couple of options we'll put in the default whatever default value should be for that state and then we just want one atom for each ID so Wilson memorize that function so that we're just producing it the first time we see that particular ID and then we'll keep our using it and we'll export that we'll call that item with ID so item worth ID is a function that takes an ID for a particular item and it returns the atom the piece of state for that item okay so we'll have to hold on to that now going to the canvas item component that we saw before which had the local state with the XY position so forth we're just going to change that from you state so you use recoil State item with ID okay and that's the only change we need to make and now we have this picture where we have lots of different components and they each have their own piece of state I'm actually just going to go in here and turn on the have a highlight updates highlight renders okay and so now you can see that when I drag this around only that individual component is rendering the corresponding thing over on the left is rendering the inspector over here's where knowing but nothing else in the tree these components are not rendering and I can just add you know any number of things you'd have thousands of things in your tree and it's oh one it doesn't it doesn't care it'll still go at the same frame rate that it was going at before at least as far as this layer of things you'll obviously have to optimize your browser compositing and all that kind of stuff but you can't really help you with that so let me turn that back off we won't really need that anymore so that's the first thing that we address with recoil it's a very flexible way of being able to keep things in sync and having shared state between components you might notice that we had we still have in the app that list of IDs and you might say well that's the linear time you have to when you insert that's going to be linear time but it doesn't have to be a list you could make a tree you can actually build any data structure you want out of atoms and it'll have this you know subscriptions will flow through that data structure and so you could have login insertions if that's what you need to do very very flexible simple building block that you can do a lot of different things with so the second issue derive data now what do I mean by that so I reset this here by derived data I mean things that are computed from State or related to some state in some way but can't independently vary so for example when I select one of these items and we change the background color so it's easier to see it gets this election blue box highlight around it and if I select multiple items then I have this bounding box that goes around all of them okay and I need to compute the dimensions of that bounding box basically and when I move one of these selected items I need to recompute that but when I move one of these other items I don't need to recompute it so I just need to recompute it when something that it actually affects it would change it now what you don't want to do here is have that be its own state you don't want to have state for the bounding box dimensions and location because then you have to get it keep it in sync with the stuff that it depends on and that's where I think a lot of people get themselves in trouble where you have sort of interdependent state you have then you need to reduce her to make sure that you've whenever you update one you also update the other you end up with a lot of ceremony around updating your state and there's a lot of potential for bugs it would be much better if we just computed the bounding box as a pure function of the things that it depends on it's like bounding boxes some function of what things are selected and where those things are but then the question is how do we efficiently recompute that function and only recompute it when we need to as opposed to like every time anything changes so in recoil we have something that lets us do that which is called a selector selector e is basically a pure function plus information about what state it depends on so that when that state changes we can recompute the function and recompute and rerender any components that depend on it so we're just gonna build that for the bounding box so the picture will look like this I said that this is actually a tree that's orthogonal to the react tree and we're starting to see that where we have this it's actually a graph and it's going to depend on some selection atom that says wet items are selected and then the state for those particular items those are all going to go into this bounding box selector which is then going to go down to some component that's going to render the blue box and so let's look at the code for that first of all we're gonna have a selector and the selector has this get which is what is called to compute the function and yet we'll have the ability to pull in the state of other atoms or other selectors and so first we're going to get the selection like what is what IDs are selected we're gonna just say get selection Adam that's some Adam which says what IDs are selected we're just gonna get its value and let's say that it's three and four we need to get the state of item three and item four so we're just gonna iterate over a selected IDs and map it to get item with ID that's the function from before so that returns an Adam we're just gonna pull in specifically the atoms that actually affect the bounding box given the current selection and then we're just going to compute the bounding box based on that information that we got what happens when we do this is what's once we use this from a component it's subscribed to just those atoms so if you change to some other item it doesn't recompute the function only if you change one of the things that we actually get on does it recompute which means that this is actually a lot more flexible than hooks because you can call them in loops you can call them conditionally the shape of that graph can change although it's not crazy because it's a pure function of the atoms state as long as all your selectors are pure functions so that allows us to compute this bounding box it'll never fall out of sync we can still just get and set the the locations of the individual components we don't have to use any fancy stuff to do that we just set it to whatever we want the bounding box will recompute if it needs to very very simple now the next thing we're gonna do with selectors is actually interpose a selector in front of each of these atoms in order to enforce a constraint on the stage so I'm going to pull out this other type of component which is a photo and you'll notice that when I resize it it stays in the same aspect ratio so I can't make it like really skinny or anything like that it just stays in the correct aspect ratio as I resize okay and so we're going to just add that functionality here without changing the UI components or anything like that by putting a selector in front of each of our atoms that make sure that that's true so remember how we had item with ID that was an atom like it's a like a function that returns an atom for each ID we have a function that returns a selector for H at each ID we're just going to change the existing export item with ID to be a selector it's gonna get some private state for the corresponding ID and there's going to apply some constraints like if it's a photo than the aspect ratio it has to be constant or something like that once we've done that we have to make no changes whatsoever to canvas item because the interface for getting a selector is identical to the interface for getting an atom so we can still just use get recalled use recoil State item with ID we can add this functionality without changing our components at all so it's very very powerful you can take something that you originally modeled this state you can say oh wait I actually need to compute the value of that and just change it to be derived without changing your components or changing a bunch of your code really good for moving fast now there's one more thing that selectors can do you can have this graph of selectors you know coming down from your state down to components and any node of the in that graph can be a sync so you can take I've got a chart component and when I drag this out you'll see that it's retrieving information from the server before it actually knows the width and height of the chart I'm just going to pull this out and we just get a spinner and then once it knows the width and height for that particular chart it's going to render it out and each one can be a different width and so we've actually taken something that was originally state that was just materially stored in our app and said oh actually not only do I need to compute this I need to go to the server to compute it this is very very very powerful you know you can take something of a state you can then make it derived you can then say oh I need to do that in a web worker I need to do that on the server or vice versa so you can pull something in from a sink and make it synchronous or even conditionally asynchronous like something could conditionally be synchronous or asynchronous you do all that stuff super flexible recoil guarantees that you won't have any race conditions or anything like that when you're using asynchronous code because we still model it as pure functions so like that selector is supposed to be pure except it can it can go to the server to evaluate the function but if you see the same inputs recoil will always give you the same outputs and will always just do one request for each each input value that's seen so you don't have to worry about like race conditions if you move away from a state while the request is happening we care of all of that so all you have to do to use it is just return a promise from one of these selectors and we do the caching all that stuff for you super super simple and powerful so the third thing that Rijo provides is app wide observation now you might notice in this demo that up in the URL I have this document ID and every time I change something that document ID increments there's a component in here that is listening for all state changes across the app and then it saves the entire state and puts the ID for that thing that's saved in the URL which means I can like copy this and send this URL to a colleague and we'll just give that a mental load in the background but they will see the same state that we were looking at because we've saved it and we've put it in the URL there very simple functionality to sort of do anything that's dependent on observing all state changes whether it's time travel debugging or persistence that kind of thing and we just provide some hooks for that you can use transaction observation and you'll be notified whenever a react commit happens that was caused by a recoil updating and you'll get the like which atoms were modified like anything so I go here and we're looking at that same document you know you can then attach metadata to each atom so you can provide whatever settings are relevant to you as far as do you want that atom to be persisted do you want it to activate the back button or you know you go crazy with that and there's one more thing you can do you can actually work with state snapshots so I have this preview button here and when I click it it like hides the toolbar so now we're like previewing what's there instead of editing it and I you just click that it's a very fast client update but you'll notice when I hover over it that's actually a link and it goes to this URL where a preview is true instead of being false so I can actually just open that link in a new tab and when this tab opens preview will be true instead of false it won't be in that other state so you can do a lot of really powerful stuff like that that not a lot of single page apps do because it can be pretty tricky with recoil make it very easy so you can actually build for yourself out of the building blocks we provide something like this link component where instead of an href it has a state change I'm going to like set preview mode Adam to true then when you click the link it just does this but when you it but it also renders out a URL that is four that corresponds to the state that you would go to if you executed this and so in this background tab I'm now in preview mode really powerful stuff like that so anyway go check it out recoil J s org now we're putting this out in a very very early state it is experimental it's not sort of a very officially sanctioned type of thing and we're still learning the ropes when it comes to open source and how to how to make that work but if it's something you're interested in kick the tires and we'd very much like to get your feedback I've got co-authors Douglas Armstrong is one of the most amazing people I've ever worked with Christian Santos does a lot of the async stuff on recoil also made the website and Docs which are very cool and so yeah I hope you check it out one other thing I wanted to mention is that the API of recoil is designed to be compatible with concurrent mode which has been a real challenge for other state management libraries we're not quite there yet but we have a definite plan working with the react team to be concurrent mode compatible in the very near future so yeah that's about it thanks so much well thanks for a dunk was amazing Thanks so we have a few questions you know we can start with one this first one how is this different than mob X but with some extra steps like manually taking care of what is observing now what how is it different from mob X it's much simpler and it's potentially compatible with concurrent mode so that's something we're gonna roll out very soon existing state management libraries are inherently not compatible with it so someone else says seems to me that you could create atoms for just specific pieces of state and shared for specific nodes yes yes so you know I watched this talk I guess twice now and as someone who is at a much more beginner level with react I guess kind of where would I end up using this brah I haven't found a I thought of a whole lot of reasons why I would be using it so maybe if you can just expand on that a little bit yeah it's you use it basically if you run into issues where the relationships between your components don't correspond to you just the like single hierarchy that you have just written out in and react react is all based on this model where you have these nested components so if you imagine something like the Facebook newsfeed you have a feed and in each feed there is a post and then within that post there's like comments and each of those things it's kind of self-contained and react is really good for these like self-contained types of scenarios and recoil lets you do stuff where it's like I got a chart over here and I've got a bunch of settings over here and so when I change the settings like certain things over in this other part of the app need to change but they don't really have any nesting right so what I would say if you're new to react is just use react and eventually maybe depending on the app you're writing you'll hit some type of limitation and then recoil could be helpful all right I'm finding more other questions here now so do you imagine this replacing the need for Redux entirely or do you think some projects are better suited for recoil while others are better suited for Redux yeah basically if you want to do things in sort of Redux manner where you have an action stream and you are reducing over that you can actually do that in recoil and so what recoil can do is like a superset of what Redux to you okay do hooks like recoil or use recoil State make it more difficult to unit test components no you can you can I mean there's different ways to you know it depends on what you're trying to do with the the unit test but we have we use it pretty extensively and I think about 30 tools inside Facebook many which have unit tests and you just do the types of things you do with any unit testing any cook space component Susan Aaron about the the recoil hooks that make this rule yeah and Mike who asked that question went on to say we effectively baked in a dependency on external into a component previously with redux or mob ex-state was injected into props and that allowed for unit testing so so I mean you can still have that separation that is conventional with redux where you have sort of one layer of the component that's getting the data and then another that's just kind of add on rendering component if that's helpful for you if you don't want to do that and you still want to sort of inject data you can you mock that hook or something like that and we could it'll be very easy to make sort of a little utility that does that for you okay and here's one that's a little bit over my head and this is a probably a good question though are you familiar with or did you draw any inspiration for recoil from the incremental Oh camel library by Jane Street no unfortunately I haven't seen that okay I'll have to check it out can you elaborate on why it's simpler than simpler than mob X and why not why you wouldn't make current state management libraries compatible with concurrent mode instead of creating a new state management library and a new API to learn well one of the great things about it is that there's not a lot of API to learn because it's meant to very very closely match the react API and this is sort of in contrast to mob X which has to almost invent its own programming language because it's trying to sort of put this data flow model at a much more intrinsic level sort of really integrate it socialism tactically with JavaScript and so I would say you'll find that recoil is very very easy to learn and you can use just a tiny amount of it in your app as to why existing state management libraries aren't compatible concurrent mode it's because they have this external pile of State that's not updates to it or not scheduled by react whereas recoil is actually just using react local state under the hood is recoils main concept similar to reiax context api or based off of it it yes I would say that the basic thing in recoil is sort of a multi context where has one value and then consumers can consume that value per provider recoil is sort of like a provider that can provide any number of values that can each have their own consumers and then everything else is just built on that so the selectors are built on that and so forth that's the fundamental thing that adds to react are there any API parts or limitations you're currently working on which recoil cannot do right now we're having lots of stuff we're releasing this in a very very early state where it's it's definitely got a bunch of limitations we need to work on some of the stuff about persistence that I showed that's works but the API we're gonna change it around quite a bit we want to make the memory management more flexible than it is currently we're still working towards concurrent support we have confirmed that it's possible to do hopefully in like the next few weeks that'll be totally finished and there's a bunch of things we could add for example we want to do eventually like incremental computation of selectors the API doesn't preclude that but we haven't haven't implemented that yet lots of other stuff looking at the experimental Facebook experimental repo on github right now and is there gonna be a place like an RFC for this yeah we're putting that into place I personally haven't done any open-source work at Facebook before or really anywhere in a while so I'm kind of learning the ropes on that stuff and we're putting it together as we go but yeah there's going to be basically similar to sort of react swear this is github based for proposals okay this is coming out of Facebook open source yeah okay experimental project sure can multiple components using a single atom be encapsulated and reused say creating 10 copies of them yes and is this if someone asked is this now or almost production-ready i would probably say no and this is again experimental its experimental we use it for a lot of internal you know a few dozen internal tools that Facebook and have been for about a year so there's a way in which its production ready and then there's also away in which there's like major limitations and you're probably gonna hit some bugs there's stuff we're still really working on I would assume that Facebook has many properties which you guys are able to test this upon and for those it probably works okay but you can't test it against you know all the different use cases other people would have for it or right how about anyone else here on the panel do we have any questions from you guys the you talk to the Iraq team and you think some of the concepts we'll be integrating to erect itself I'm definitely talking with them and that's a really good question that I also have great have they been receptive to the idea so far yeah definitely you said existing state management libraries are not compatible with concurrent mode is that is that the case with relay as well relay is I'm not sure exactly what the status of relay is that I know that the relay and react teams worked very very closely together and so there's stuff that's being added to react to sort of support relays use cases and so forth so I strongly suggest by the way using relay for the types of things that it's good at you know that's a question I get about recoil a lot actually oh well I can do these asynchronous selectors so like when should I use that when should I use relay if you have some something's like backed by entities in a database that you need to mutate or you want to have sort of an object graph of them relays really good for that and they do a lot of really smart stuff it's a very complicated domain whereas the facilities in recoil would be useful for sort of other miscellaneous requests that you might want to make an example they've used in previous talks is like if you want to type the head or something like that I think that exhausts most of our questions here I think people are still trying to kind of wrap their head around this but the talk is a little bit fast-paced and so I'll probably go over at many times but yeah if we don't have any more questions or if anyone on the panel doesn't have questions I think this kind of represented most of the questions I had great thanks so much David was a great time yes yes thank you thanks for joining us a four day for dinner soon
Info
Channel: ReactEurope
Views: 88,506
Rating: 4.9659705 out of 5
Keywords:
Id: _ISAA_Jt9kI
Channel Id: undefined
Length: 33min 30sec (2010 seconds)
Published: Thu May 14 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.