Mastering React Context: Do you NEED a state manager?

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
react context what is it when should we use it does it replace a state manager hi i'm jack harrington a principal full stack software engineer and on this video i'm going to show you all about react context i'm going to show you three different ways to manage state using react context we're going to compare react context versus a state manager and then i'm going to sum up by giving you some recommendations about when to use it when not to use it and when to use a state manager instead let's get right into it okay so i'm over here in my 2022 directory and i'm going to do a yarn create and then next app and i'll do mastering context i'll be the name of our next app i'm using next this time instead of create react app because i want to be able to have multiple routes where we do the same page in multiple different ways using state using context using a state manager and next just makes it easy to do multiple routes like that okay now i'm going to bring that up in vs code and then i'm going to do yarn dev to bring up the server and there we go our next app easy peasy lemon squeezy all right let's first off let's learn a little bit about context and i'll start by just creating a simple counter using state as we've seen before and then we'll migrate that to use it as context so i'm going to do is create a directory here called counter and this is going to have our counter examples we're going to do two different types of examples in this video so we'll do counter use date and i'm just going to export a default function here called counter use date this is going to be our component i'm going to say hi and now let's go over in our browser and check it out so i'm going to go into the counter routes and then counter use date and we've got our high over there perfect awesome and that i love next for that sort of stuff so let's make a counter i'm going to go and first create some state for that so bring in use date and then i'm going to create two components we will have an add one button component add one button and let's not worry about the props for the moment and in that we're gonna have a div with a button this is add one so now let's go and create some state call it counter and we'll have set counter this is gonna start off at zero and then in order to add one to that right we're gonna need to have set counter in there so let me bring in that component add one button and we'll have to pass along that set counter as a prop so just do set counter and then send it over as a prop and now over here in the on click we can say when we're clicked we're going to take that set counter we're going to take the current value and we're going to add 1 to it so we don't actually need to send along the counter because we're going to get the current value right here so that's just a nice way to save at least one problem now we're going to need a counter component it's going to show us what the current state of that counter is and that'll also be a div or put in counter and we'll give it the counter and now down here we will have our counter component cool all right let's check it out and see if it works yeah nice all right and we can just go add one nice all right to make things a little bit more visible i'm actually going to change the styles here a little bit globally i'm going to make div i'm going to style div globally which is not a great thing to do but it's fine in this case i'm going to do a little bit of padding on it and a little bit of margin this is going to help us when we look at when the different components are rendering because we can look at that visually using react dev tools and then i'm going to put some border on that so i'll just put one pixel of you know kind of a light gray nice okay maybe i'll put a little radius on it so it's not so harsh ah nice all right so those are our components i've got the outside div and inside div that has the two divs in it one that has the add one one as the counter so now let's say that we have some containing component in here we'll call it container and it's going to have a div and then within that div it's going to have that add one button now of course add one button needs set counter so we need to send that along but we don't have it so we're going to need to take it as a prop and of course we need to go use it so let's set that to container and now we've got an extra level of containment around add one but it still works and that's fine but this is called prop drilling because you're drilling a prop through and in particular this container doesn't know nor does it need nor does it care about this set counter the only reason it's getting set counter is because add one button needs that and this is where most people say okay cool i'm going to use context so what's context well context is a way of defining up at this level that we have some shared state some shared context some shared values that we're going to percolate down to any component within that hierarchy that needs that data so let's go do this in a new file so create a new file in here called counter and then ctx for context and then use state because we're going to use use state to drive the context i'll just paste that in there so the first thing we need to do is create a context so we'll do create context and what we're doing here is creating essentially a type of context because you can have multiple instances of the same context on the page so we're going to create a type of context called counter context using create context and then you've got to give it an initial state and that's going to be the same initial state for all the instances of that context so we'll just use null there and then you need to get it into the tree and the way that you do that is you create a provider so i'm going to create a counter context provider which is a component and that component is going to take children which are the components that are nested within it and then i'm going to do contact countercontext.provider and give it the value which in this case is just going to be a use state of 0. so that the value of this counter context is going to be the tuple that's returned from use state the first value being the value and the second one being that setter now within this i want to go and bring in those children and then close up that counter context provider and now i can wrap my container and my counter in that so put that provider in there and then save that now an interesting thing about this is you actually can't access the context at the level of counter use date you can only access it in the sub components which is fine in this case because container and counter are both sub-components so all right so how do we get access to this how do we get access to the current counter context that we're passing through so we'll do use context and let's see so first we'll start off by changing add one button so i'll make this into a function that has a return statement and then up here i'm going to go get the and then here i'm going to go get the counter and the set counter state by using that context so that's going to give me that value that's set up here in the counter context provider now all i really need is the set counter state which i'll just rename to set counter like so and i'll get rid of this prop so i'm just going to avoid that first value that's fine and now the nice thing is we can get rid of this set counter here and here because we don't need to pass it through as a prop anymore we don't need a prop drill and then finally over here in counter we need to go and get rid of our prop coming in and then again turn this into a component that returns and now we're going to do the same use context but this time we're going to get that value so let's see counter and now cool we can get rid of all the props down here boom and boom and let's try it out so let's uh go over to our page and then we'll do ctx use state all right looks good and let me hit add one a bunch of times cool so we don't have to do that prop drilling which is really nice okay so before we move on i do want to talk about context and how you can use multiple instances of it i think a lot of folks are used to using context right at the top of the app there's a whole bunch of providers right at the top and they provide global context down to the different components that need them but you can actually use context at any level and you can use multiple instances of the same type of context so for example i'll go down here and we'll make this not the export default and then i'm going to export a new default function called counter use date page that returns a div that contains a bunch of counter use states so i'll make well let's say four of them and then we'll try it out hey cool all right so you had one there add one there add one there awesome and they're all self-contained so it is interesting to think about using context in different ways either from the global perspective where you give context down to the entire tree or even within a small set of components where you might not run into the same performance problems you do when you do it at the top level but we'll get into that in a bit so next thing i want to show is the second variation of how to use context so we've used use state so far next one i want to show is use reducer as a source of context so let me go and copy and paste this again of course all this code is available to you on github for free so the counter ctx in this case use reducer so why do you want to do this well if your state is a bit more complex then you might want to use use reducer as opposed to use state because we normally use use state just to have one value a string number boolean object something like that or an array what happens when you want to have multiple values in there or you want to have multiple different things that you can do to the values in the reducer so okay so let's do a use reducer in here we'll change use state to use reducer now use reducer does start off with an initial value but it also has a reducer function the pass in and that reducer function takes the current state and then an action and what most folks do is pass a type the action is an object and so there's a type on there and we'll say okay cool so if the type is add then we will return that state the current state plus one otherwise we're just going to return the current state and we'll finish up our switch like that cool okay now use reducer returns two values it's a tuple just like use state the first one being the value and the second one being the dispatch so where before we had a state setter now i've got a dispatch function so let's take a look so counter is going to be fine because the first thing it peels off is the counter but add one button that's not going to be fine so we'll do dispatch in there and then the dispatch takes that payload so in this case we'll do type and then we'll say add because that matches the ad up here so let's see how that works so i'll go over to our page do reducer in here and we'll give it a try and that works just fine now to go and add on top of that we could for example add another case in here for subtract and return state minus 1. and let's go and add a another button so we'll have add one button and a subtract one button say subtract one and this one will fire a type of message that says subtract and then the last thing we need to do is actually use it so let's go over here to our container and then drop that let's in there look all right now we've got add one and subtract one and they do exactly that pretty cool now before we continue on i know i'm going to get some feisty comments about this u-state example because really you don't need to do this prop drilling and so i do want to show where you use container the correct way and you don't need to do prop drilling and you don't need actually to use context so let's clean this up a little bit before we move on to the whole state manager thing so down here you see we have our container what we really want to do is we want to have our container just be a generic container right so it's probably going to add some sort of markup div or whatever onto it and then within that we want to have our add one button that takes our set counter and the way that we do that is we do exactly the same thing as we did with the providers we take children in here and then we just render the children where we want them to go inside of this container hierarchy all right let's take a look over at our used state just to make sure that it works perfect and now you don't have that prop drilling right because your container used a page has everything that you need at the top level and this way that you avoid prop drilling and you actually get a nice small concise component so as i mentioned you can use context at the top level of your application which gives you all of that state globally which is awesome and that actually makes it kind of compete with a state manager most folks use a state manager like redux or jotai or zuschand or mobx to globally manage state and then the question becomes is context a state manager so here's my take on this context can manage state but it is not a state manager a state manager is a library that is specifically built to manage state it's highly opinionated about how you structure that state how you request that state how you mutate it both synchronously and asynchronously and it makes sure that when you have a state change only the components that actually reference that particular piece of the state get updated and all of that can be done in context but isn't out of the box and that's the big value of a state manager is all that comes out of the box for you but to make it more clear let's go and actually build out a state manager example and what i'm going to do is actually build a different example it's going to be an e-commerce example that has a login and has a cart count and then we're going to be able to log in log out and add one to the card not a huge example but it's a bit more robust so we can kind of see the value of a state manager so let me save this and then we'll go take a look at the state manager we're going to use and the state manager we're going to use is daishi kato's zushgan it's a fantastic lightweight micro state manager it's one of a family of microstate managers all maintained by daishikato who's got a book coming out in march i recommend you check it out i'm buying it myself i'm really excited about it can't wait to read it all right so the reason that folks like zustan a lot is because it follows the unidirectional style of redux and it does it without actually all that redux boilerplate so let's give it a try in our app as the first take on our ecommerce application so let's go back over into visual studio code and i'm going to stop the app and then i'm going to add the zeus don dependency so yarn add zeus done and then i'll do yarn dev again and then i'm going to make a new directory and we'll call it e-commerce and within our e-commerce we're going to create a page we'll call ecommerceztand.js and we will export a default function called zeustonpage and we'll return hello just to make sure we're routing properly so let's go and check it out over in our browser all right looks good i want to go and build out the ui first there's gonna be four sections to our ui we're gonna have a login section we're gonna have a section that shows the username and then there's gonna be an added card section and a section that shows the cart count so let's go and create those first i'm going to create the login section and it's going to have a div where i've got two buttons log in and log out we're going to have our user section that has a div that says user we're going to have our add to cart section that has add to cart on it and then we're going to have our cart count section that displays the cart count and finally we'll just wrap all those up onto the page so we'll have the login section first we'll have the user section then the add to cart and then the card count all right let's take a look at our page cool nice but of course it doesn't do anything so let's go and build out our zooched on store so we'll go over here to the ecommerce directory create a new file we're going to call this store dash zustan because we're going to have multiple stores and we're going to import create from zusdan now what create does is it builds us a custom hook pretty exciting right so create a custom hook called use store and we'll use create for that now create takes a function that function takes a couple of parameters the first one of which is set and we'll get there in a second as to what that is but at the moment we're just going to return the initial state object so that's going to have the user which is going to be an empty string and then we're going to have the cart count which is going to start off at zero and then we're going to have a set of functions we're going to have login which is going to set the user so we'll have set the state to have a user on it in this case the user's jack we'll have a log out basically does the exact same thing except sets the user to an empty string and we'll have an add to cart which takes the existing state and adds one onto the cart count now for these two we'll just clean that up and don't need that all right that looks pretty good now let's re export this as the default and then we'll go over to our page and we'll import this from storezustand and now we need to use it so over here in the login section we're going to return out our buttons and we're going to use that custom hook to go and get the log login and logout methods so first we get login and so what we do is we do ustore and then we give it a selector and that selector says what we want from it in this case we want the login we'll do the same thing with the logout and now we can give them over to the buttons so let's see on click here should do log in and on click here should do log out okay and then let's implement our user section so again we'll return that and then we'll do the user equals and then we'll get select out the user from the store so let's add that in there and let's try it out oh beautiful okay so now when we hit log in we get jack and when we hit log out we get nothing perfect all right let's do the same thing for the cart section and now we're going to get add to cart and get this add to cart off that state and we'll do the same thing for the cart count and then i got to go and set this to be cart count and with the current card count and then i've also got to go up here and add this as the on click let's go check out on the page perfect nice okay so pretty easy although i have to say that there's a disturbingly strong connection here between the ui and the structure of the store and that's going to make it harder for us if we want to migrate to another technology let's say that zustan isn't serving our needs anymore so let's go and create an abstraction layer or a facade between the zusdand store and the ui so the way that i'm going to do that is go back here into the zustan store and i'm no longer going to export to the store directly but what i am going to export is a set of selectors so i'll export a use login which is a custom hook that just returns the exact same selector as we had before and then i'll do the same thing with log out and add to cart as well as user and finally cart count all right so let's go back to our page and bring those in i'm going to replace use store here with use login use log out use add to cart use user and use card count and then all i need to do is just replace these direct use storage locations with our new custom hooks and that's going to provide a level of abstraction as we maybe change out zustan for context so let's go down here to add to cart use user and finally use cart count all right let's try it out okay now everything's working and if i hit add to cart that works too all right so now we've got a nice level of isolation between our zustan store and our ui and if we want to go and change that out then we are free to do that but before we do that before we go and change that out for context i do want to show you another big advantage of a state manager which is demonstrated if i go over here to the profiler which is a react dev tool integration and then in there i go to the gear icon and then within that i highlight updates when components render so now when i hit add to cart you can see that only the cart count renders this button doesn't re-render the only thing on the page that changes when i hit add to cart is that cart count so similar sort of thing up here with login i just hit login and the only thing that changes is jack even though the store which contains both cart count and the username are changing if i hit log out then again that only changes and another cool thing is it's smart enough to know that the value hasn't changed so if i keep on doing set state and i set it to the same value then we're not going to get any updates so it really contains the amount of change that happens even when that state could globally impact the entire hierarchy so that's what we're looking for in context or looking to see if we can replace as we get into context can context match that same level of granular updates next thing we need to do is go and take our e-commerce zostan page and create an e-commerce context page so create e-commerce context page and call that context page now currently it's bringing in the zustan store so we're going to want to replace that store with a context-based store so we'll create a new file called store context and i'm just going to go and comment out the zushtan store and we're going to go and replicate the behavior of that store with our own custom hook so in order to do that i'm going to need to bring in use date because we're going to need to store some state and then i'm going to create a new custom hook called use store and right at the top we're going to have our user inset user we're going to have our cart count and our set cart count and then we're going to return that return user and return card count and then we need those login logout and all that so let's go and build those out as well so i'll do login and that's going to set that user to jack do log out which will set it to an empty string and then finally add to cart which is going to just add one to the card count all right so now we've got our equivalent of that store but as a custom hook so now we need to provide that down because we're doing context this time so i need to create the context first we'll do create context and just like before i'll create a let's say store context with create context and i'll just use null to start with and then next we need a provider so i'm going to export a const in this case store context provider again we'll take children and then we'll invoke store contact provider and give it that use store so that's going to be the value is going to be all of this these user card count and all that and then we're going to have children in there and finally close up that tag cool and the last thing we need to do is port these because we no longer have use store as a zouched on the function although we do have use store we now need to go and get that store from the context so let's do use context and then port the first one so we'll do use context and then we'll give it the store context which is the first thing it needs and then what i get off of that is that object so i'll just dereference log in from that and that is the equivalent so we'll do that but we'll change all of these to have use context and then dot and then that value and there we go so now we've got a store that uses context so let's go over here to our ecommerce context page and then bring in the store context but of course we need to bring in that store provider as well so do that store contact provider and now what i'm going to do is i'm going to wrap it so i'm going to not export that as a default function i'm going to i'm going to create a new export default function called context page wrapper that's going to return that context page but wrapped in a context provider all right let's give it a try and see how it goes so we'll do ecommerce context perfect let's see log in log out add to cart all right it's all working but as you can see there's a whole lot of updating going on so anytime i hit add to cart basically the entire tree re-renders and that's because the entire tree actually really depends on that context a common misconception about context is that anything from the context provider down re-renders on any context change that's not actually the case it's only when your component actually uses that particular context that's when you will get updated but again i mean that could be a lot of subscribers so anything that changes about the context is going to force a context update so how do we get around that well we use another awesome daishikato library this one is called use context selector so let's go and do that so i'm going to go back over here to our terminal and now i'm going to bring in use context selector and i'm going to create a new file called e-commerce context selector dot js so that's a good start now i want to go and take that store context and replicate that using this new used context selector so we'll create a store context selector js file i'll paste that in there and there's a couple of small changes we need to make so we're going to bring in create context from that use context selector so you basically are overriding the react create context with a custom create context from this library and then the other thing we need to bring in is use context selector so instead of just use context we're now saying we want a context selector we want to go and select just what we need from that context so let's go and use that down here the everything stays the same except for these use context so instead of use context i'm going to use use context selector and then you give it the context but you also have to give it a selector function so in this case i'll highlight that and now we'll do the state that's coming in and we want the state dot in this case something like login logout all that i'll finish up that function and there we go a little bit uglier i mean it's got a wrap but otherwise it's fine so basically you got a context selector you give it the context and a function that function gets a state and then you peel off whatever parts of that state you want we get use context there and let's go over to our e-commerce contact selector and then use that as the store and then we'll jump over to the web page and now i'll add on selector to the path looks about the same but now when i do add to cart less changes which is cool now we're not seeing any change on user but we are seeing changes on these and why is that well if we go back over here to our store context selector and we look at the way that i've structured my custom hook i'm creating a new function each time i go through use store and every time i make a change i'm reinvoking usedor and of course the content of these functions is exactly the same but the reference is different i'm creating a new reference every time and it's referential identity that matters here so what we need to do instead is wrap those i'm going to use use callback and i'm going to use use callback to create them but maintain the reference as long as the values haven't changed so in this case i'm going to only create that callback once because i'm going to give it an empty dependency array the same thing for log out and for add to cart i am going to give it a dependency on cart count because otherwise you're going to get a stale closure because when card count starts it's at zero and if i don't update this function then i'm going to get exactly that same function that's going to give me one over and over and over again so i need to add on that dependency of car count all right let's try this out all right log in log out okay that's looking good now it's constrained to just user but we are getting an update to the wrapper which is actually unavoidable in this case but i don't think it's that big of a deal that wrapper isn't actually that big of a component it's just the store context provider and then the invocation of the context page and the context page itself isn't actually re-rendering so it's not actually that bad okay so now add to cart is actually rendering the button again and that's because we are generating a new version of add to cart function every time that this cart count changes so to avoid that we can go back over here to our contact selector and then instead of using this specific value we can use a relative one v equals v plus one and now we don't need to regenerate that function every time it'll just take the current value and add one to it and let's take a look and nice okay pretty pretty clean pretty solid i like it okay so now let me give you some recommendations now that we know more about what react context is and how to use it and have compared it to a state manager so when it comes to react context without something like daishikato's use context selector it's really good for slow moving data that you're going to put up globally things like theme and user the kind of things where when it changes everything is going to redraw anyway so it doesn't really matter if it's high velocity data at the top level that might be a problem depending on the number of subscribers again remember that when context changes only the things that depend on it actually get updated so it may or may not be an issue but that is something to think about when it comes to context now you can also use context at the local level within a couple of small components way down deep in the tree and i think that's very good no problem there at all when it comes to state managers i do recommend using state managers when you are on a team and you want standards and things like redux and mobx are a lot easier to pr because there's a right way and a wrong way to write those and you also get that runtime benefit of having the selectors where it's pretty easy to understand that when this changes only that's going to change so i think when it comes to large teams that's when a state manager is really going to be beneficial now when it comes to app shells and microfront ends i do have a specific recommendation there and that is to use context for that and that's because i think you should have a very small footprint of data in that space the current user the api access jot and then maybe enough to drive the header or things like a card count you really don't want to put in a lot of global state and when you have a state manager you're tempted to potentially have a very large state footprint inside of the shell itself when the shell really should have just the minimal amount of data that's required to run all of the sub-components and get them all you know talking together and that would again include user the user jot and maybe some small pieces of global data well i hope this has helped you understand react context and how to use it and when to use it and how it compares to state managers if you have any questions or comments be sure to put those in the comment section down below if you liked the video hit that like button and if you really liked the video hit the subscribe button and click on that bell to be notified the next time a new blue collar coder comes out
Info
Channel: Jack Herrington
Views: 77,056
Rating: undefined out of 5
Keywords: jack herrington, react context, blue collar coder, jack herrington usecontext, useContext, Mastering Context, mastering context, Context Versus State Managers, react usecontext, react state management usecontext, react state management, master react usecontext, master react context, react useContext, react context api, Context Using use-context-selector, When Context Updates Components, Context Using Prop Drilling, context api, context API
Id: MpdFj8MEuJA
Channel Id: undefined
Length: 37min 25sec (2245 seconds)
Published: Mon Feb 14 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.