Coding

useContext() + useReducer() = Magic?

  • 00:00:01 hi and welcome to this video you might
  • 00:00:04 have seen my first part where we built
  • 00:00:06 this tiny app or where we actually had
  • 00:00:09 this tiny app here and moved it from
  • 00:00:11 Redux to the react context API but we
  • 00:00:14 use classes and isn't using classes so
  • 00:00:17 2018 well let's do it the 2019 way and
  • 00:00:21 let's convert this to use react hooks
  • 00:00:23 and functional components everywhere
  • 00:00:28 so for this we'll need to know what
  • 00:00:32 react hooks are obviously and I created
  • 00:00:35 a video and an article which you should
  • 00:00:37 definitely check out and to which you
  • 00:00:38 find links in the video description
  • 00:00:39 where you can learn the most important
  • 00:00:42 things about react hooks from scratch no
  • 00:00:45 prior knowledge about it needed so it
  • 00:00:47 you assume that you know what react
  • 00:00:49 hooks are for this video so how can we
  • 00:00:53 use hooks here then we got classes
  • 00:00:55 everywhere like here in the global State
  • 00:00:57 in the app component here and also in
  • 00:01:00 our card and products pages everywhere
  • 00:01:03 we got components time to change this
  • 00:01:06 now let's start in the App J's file and
  • 00:01:09 let's convert this component here this
  • 00:01:11 class-based component into a functional
  • 00:01:14 component and for this I'll create a
  • 00:01:17 constant and name it app which will be
  • 00:01:20 Errol function that receives props and
  • 00:01:22 that does not have a render method here
  • 00:01:25 but just a return statement that returns
  • 00:01:28 some JSX
  • 00:01:29 and converting this component here was
  • 00:01:31 fairly trivial because there's nothing
  • 00:01:33 in here which would make that complex we
  • 00:01:36 can now get rid of that component import
  • 00:01:37 up there now that was simple let's move
  • 00:01:41 on to the products J's file and here
  • 00:01:44 I'll do the same I'll name this products
  • 00:01:47 page and this will now be a functional
  • 00:01:50 component that simply gets some props as
  • 00:01:53 all functional components and that has
  • 00:01:55 no render method of course so let's get
  • 00:01:57 rid of this and with that changed we can
  • 00:02:00 get rid of that component import and
  • 00:02:02 yeah
  • 00:02:03 Dada's is already that is it nothing too
  • 00:02:07 fancy here I'm using shop context
  • 00:02:10 consumer in a way that worked in
  • 00:02:12 functional components all the time so
  • 00:02:14 even before react hooks were added and
  • 00:02:16 therefore no adjustments are required
  • 00:02:18 here now that differs in the cart JS
  • 00:02:21 file here we got component did mount
  • 00:02:24 though I'm only doing some logging here
  • 00:02:26 but more importantly we're getting
  • 00:02:29 access to the context with the static
  • 00:02:31 context type here and that is not
  • 00:02:33 supported and functional components so
  • 00:02:35 let me name this card page here and
  • 00:02:38 again it's a function that receives
  • 00:02:42 and that will have no render method and
  • 00:02:44 therefore gets rid let's get rid of
  • 00:02:46 render but the problem is component it
  • 00:02:49 mount and this static context type this
  • 00:02:52 will not work so here we need hooks and
  • 00:02:55 we actually need two hooks here the more
  • 00:02:58 important hook for the context which we
  • 00:03:00 are really using here is use context
  • 00:03:03 this gives us access to context in a
  • 00:03:05 functional component the less important
  • 00:03:08 hook which we need here because we're
  • 00:03:09 not really doing anything useful with it
  • 00:03:11 is use effect which allows us to replace
  • 00:03:13 component it mount now let's start with
  • 00:03:16 the context here we can create a new
  • 00:03:19 constant and name it context the name is
  • 00:03:22 up to you and then you call us context
  • 00:03:25 and as an argument you pass a reference
  • 00:03:28 to your context so shop context in this
  • 00:03:30 application here which we import from
  • 00:03:33 context shop context and in case you're
  • 00:03:35 wondering what the shop context is about
  • 00:03:37 and what does entire app is about of
  • 00:03:39 course watch part 1 of this video where
  • 00:03:42 we do that read acts to context API
  • 00:03:45 transformation so now we're getting
  • 00:03:48 access to context here we can therefore
  • 00:03:50 a console.log context here but not in
  • 00:03:53 component that mount but instead we can
  • 00:03:56 use use of fact here and use effect as
  • 00:03:59 you know takes a function as an argument
  • 00:04:03 which will execute right after every
  • 00:04:06 render cycle essentially now I don't
  • 00:04:08 want to lock the context as often I only
  • 00:04:11 want to log it when this component
  • 00:04:13 renders for the first time when it was
  • 00:04:15 mounted and for that you can pass that
  • 00:04:17 second argument to use effect which is
  • 00:04:19 an array of all the dependencies of this
  • 00:04:22 function and only if one of the
  • 00:04:24 dependencies changes this will rerun no
  • 00:04:27 obviously we got no dependencies in here
  • 00:04:29 right so this is an empty array and this
  • 00:04:31 is interpreted by react such that it
  • 00:04:34 will basically never rerun this function
  • 00:04:36 here it will only run it once when this
  • 00:04:38 component mounts with that we got access
  • 00:04:42 to context we hopefully lock that to the
  • 00:04:44 console now all the places where I
  • 00:04:46 access this context down there of course
  • 00:04:50 now have to be converted to just context
  • 00:04:52 without this because context is now just
  • 00:04:55 a Const
  • 00:04:55 in this function but with that we should
  • 00:04:58 have everything we need and if we now go
  • 00:05:00 to our app here and we go to the cart we
  • 00:05:02 see our context here we see what's in
  • 00:05:05 there and if I add something to your
  • 00:05:06 cart this still works even though I
  • 00:05:08 haven't converted everything to
  • 00:05:10 functional and Hookes based components
  • 00:05:13 but that's the great thing about react
  • 00:05:15 hooks you can just dump them into an
  • 00:05:16 existing project as long as you're using
  • 00:05:18 react 16.8 and it does matter if some of
  • 00:05:22 your components use classes and our use
  • 00:05:24 hooks and are functional they work
  • 00:05:26 seamlessly together but with that let's
  • 00:05:30 tackle our biggest functional a
  • 00:05:32 class-based component that is the global
  • 00:05:34 state where I do that entire state
  • 00:05:36 management with my context API in there
  • 00:05:40 I now want to have a functional
  • 00:05:43 component which receives props obviously
  • 00:05:44 and that means that we'll have problems
  • 00:05:47 with the state here because state
  • 00:05:49 management with this state property and
  • 00:05:51 with this said state is not supported in
  • 00:05:55 functional components it never was and
  • 00:05:57 it isn't with hooks now we can simply
  • 00:06:01 use the use state hook for that so we
  • 00:06:04 import use state instead of component
  • 00:06:06 and that starts here instead of having
  • 00:06:09 state as a property like this we can add
  • 00:06:13 a constant here and we'll use array D
  • 00:06:15 structuring in a second and we'll pass
  • 00:06:18 this object here to use state now the
  • 00:06:23 array destructuring here allows us to
  • 00:06:25 get access to the current state which is
  • 00:06:28 then updated whenever we change it and a
  • 00:06:30 function that allows us to update the
  • 00:06:32 state and we always get back these two
  • 00:06:34 elements here by use state we always get
  • 00:06:37 back an array with these two elements
  • 00:06:39 now the cool thing is we don't have to
  • 00:06:41 stick to one state we can actually have
  • 00:06:44 two or three or as many as you need
  • 00:06:46 States and here it would make sense to
  • 00:06:49 manage the card and the product
  • 00:06:50 separately because unlike this set state
  • 00:06:53 with class-based components when you
  • 00:06:56 update your state here with the help of
  • 00:06:57 use State react is not merging your
  • 00:07:00 update with the old state it will
  • 00:07:02 overwrite it and therefore if you have
  • 00:07:04 something which doesn't always update
  • 00:07:06 together like products and cart it makes
  • 00:07:08 sense to use
  • 00:07:09 different states so here I'll use
  • 00:07:11 another state which will just be an
  • 00:07:14 array for my cart and actually this
  • 00:07:16 products state here can therefore also
  • 00:07:18 just be the array doesn't have to be an
  • 00:07:20 object here I'll name these products and
  • 00:07:24 set products and here I'll name this
  • 00:07:26 cart and set cart because the second
  • 00:07:29 element always is that function that
  • 00:07:31 allows us to set a new state and both
  • 00:07:33 states here are simply arrays now now I
  • 00:07:36 actually don't even have to manage
  • 00:07:38 products here as a state because in this
  • 00:07:40 tiny application there is no logic that
  • 00:07:43 would change the products so therefore
  • 00:07:46 this doesn't even have to be state we
  • 00:07:48 can just have a products array here like
  • 00:07:51 this and this will do we don't have to
  • 00:07:54 use use State here for the card however
  • 00:07:56 we do because the card will change so
  • 00:08:00 let's also get rid of that render method
  • 00:08:04 down there and just return JSX
  • 00:08:07 and now how do we use our new card state
  • 00:08:10 then well first of all things like add
  • 00:08:13 product to cart can stay there we can
  • 00:08:15 defined functions and functions but we
  • 00:08:18 have to store them as functions either
  • 00:08:20 with the function keyword or here with
  • 00:08:22 cons or let so we create a variable or a
  • 00:08:25 constant that then holds this function
  • 00:08:27 so now this allows us to add functions
  • 00:08:31 in that functional component which is
  • 00:08:32 great but inside of these functions
  • 00:08:35 inside of add product cart and off
  • 00:08:37 remove product from cart we're still
  • 00:08:39 using the state incorrectly instead of
  • 00:08:42 this state cart we just have well cart
  • 00:08:45 right this year this constant which we
  • 00:08:47 use here or which we get here with array
  • 00:08:49 D structuring this is our cart and here
  • 00:08:54 this can all stay the way it is when we
  • 00:09:00 call set state and update the card
  • 00:09:01 however we want to call set card and
  • 00:09:04 pass our updated cart in there and that
  • 00:09:07 is all this should be all we need to do
  • 00:09:09 on this first add product to cart
  • 00:09:11 functions easy as that now for remove
  • 00:09:14 product from cart it's essentially the
  • 00:09:17 same instead of this state card we just
  • 00:09:19 copied a cart because there is that
  • 00:09:21 current
  • 00:09:22 hard array as set and updated by said
  • 00:09:26 card and by use state where we
  • 00:09:28 initialize it and then down there we
  • 00:09:31 change all kinds of stuff and in the end
  • 00:09:33 when we set state we set card 2d updated
  • 00:09:37 card again
  • 00:09:37 so really straightforward and simple now
  • 00:09:40 the products are just products now
  • 00:09:42 they're not even managed with use state
  • 00:09:44 but here it doesn't matter if it is a
  • 00:09:46 normal constant which is not generated
  • 00:09:49 by you state or if it was generated by
  • 00:09:51 use state we just used a constant here
  • 00:09:53 therefore it's the same for the card
  • 00:09:55 this is also a constant now it's this
  • 00:09:59 constant this here is the products
  • 00:10:01 constant and for the methods or the
  • 00:10:04 functions while we remove to this
  • 00:10:06 keyword and we just call add product to
  • 00:10:08 cart or remove product from cart here so
  • 00:10:11 these constant functions I defined up
  • 00:10:14 here and that is almost all instead of
  • 00:10:18 this props children it should just be
  • 00:10:20 props children and therefore now if we
  • 00:10:23 save this we should be able to still add
  • 00:10:26 stuff to the cart and do it there and
  • 00:10:29 also remove it from the cart this works
  • 00:10:32 just fine and it works as it did before
  • 00:10:35 but now with functional components and
  • 00:10:38 our hooks now we're not done yet though
  • 00:10:42 this is of course nice transformation
  • 00:10:45 just a quick check this here was a
  • 00:10:48 functional component so we're only using
  • 00:10:50 functional components now which is great
  • 00:10:52 but instead of using
  • 00:10:54 use state here which is perfectly fine
  • 00:10:57 in the global state we can also have a
  • 00:10:59 look at another hook provided by react
  • 00:11:02 and that would be the use reducer hook
  • 00:11:04 now the user reducer hook is also a hook
  • 00:11:07 related to state management but it
  • 00:11:09 allows us to create a more complex
  • 00:11:12 reducer function that receives the old
  • 00:11:14 state and an action and spits out a new
  • 00:11:16 state and in the end that allows us to
  • 00:11:18 manage this at product cart and remove
  • 00:11:21 product from card logic in a bit of a
  • 00:11:23 more elegant way and to outsource it
  • 00:11:27 away from our component here even if you
  • 00:11:29 want you for this I'll create a new file
  • 00:11:31 here which will name reducers j/s which
  • 00:11:34 is totally optional you don't have to
  • 00:11:36 create that new file here and in
  • 00:11:38 reducers j/s I want to basically have my
  • 00:11:42 my functions in there so I'll copy them
  • 00:11:44 in there though we'll have to adjust
  • 00:11:45 them of course because we're using
  • 00:11:47 things here which are only available in
  • 00:11:50 our global state component and not in
  • 00:11:52 every random file where we add them to
  • 00:11:54 now I want to use these functions here
  • 00:11:56 in a reducer function which I create so
  • 00:11:59 I'll treat yet another new function here
  • 00:12:00 and I'll name this or I'll export this
  • 00:12:03 as a default here export default shop
  • 00:12:10 reducer and that is a function in the
  • 00:12:14 end that will get a state and an action
  • 00:12:17 and then should return something it's
  • 00:12:19 actually export this as a named export
  • 00:12:22 to make this really clear so here I get
  • 00:12:26 a state and an action and this will be a
  • 00:12:28 function that we can later use with use
  • 00:12:31 reducer now I will first of all check
  • 00:12:35 which type of action we have so I expect
  • 00:12:37 the action to be a JavaScript object
  • 00:12:39 that has a type property that defines
  • 00:12:43 which action we're executing here and
  • 00:12:45 this again is basically logic you might
  • 00:12:48 know from redux where we also have
  • 00:12:50 actions with the type so here I switch
  • 00:12:52 on the action type and I check my
  • 00:12:55 different cases and let's say here I
  • 00:12:57 define my cases up there so our export
  • 00:13:01 constants and the first one could be add
  • 00:13:04 product which in the end just holds that
  • 00:13:08 string here as an identifier and then we
  • 00:13:10 have remove product here which holds
  • 00:13:14 this string as an identifier so we have
  • 00:13:17 these two constants here which we can
  • 00:13:19 use as action types and I'm just storing
  • 00:13:22 them in separate constants so that we
  • 00:13:24 can simply import and use them
  • 00:13:26 everywhere and we can't miss type on the
  • 00:13:28 string identifier and here I never check
  • 00:13:32 for add product and if that is the case
  • 00:13:35 I return a certain state if we have a
  • 00:13:38 number case namely remove product I will
  • 00:13:41 return yet another state and in a
  • 00:13:44 default case so that we have some our
  • 00:13:46 actions I simply return the state as we
  • 00:13:49 get it so
  • 00:13:49 untouched basically now an ad product I
  • 00:13:53 of course want to return the state as it
  • 00:13:55 is spit out by ad product here so I want
  • 00:14:00 to call that function here and that
  • 00:14:02 function requires a product as an input
  • 00:14:04 but this function will also require cart
  • 00:14:08 as an input the old cart so what I will
  • 00:14:11 do then there is I will return at
  • 00:14:14 product to cart and for the product that
  • 00:14:18 I need to pass and I expect that to be
  • 00:14:20 part of my action that action just needs
  • 00:14:22 to have a product because how could I
  • 00:14:24 add a product that I don't get so this
  • 00:14:26 is a reasonable requirement the cart is
  • 00:14:29 part of my existing state because this
  • 00:14:31 reducer will be used for managing the
  • 00:14:33 cart and therefore state will have a
  • 00:14:35 cart property let's say or the state
  • 00:14:38 itself is our cart that could also be a
  • 00:14:40 reasonable assumption so state is our
  • 00:14:43 car terrain we could go with that as
  • 00:14:45 well but to make this a bit clearer I'll
  • 00:14:47 say start has a cart property so state
  • 00:14:49 is an object which has a card property
  • 00:14:51 which in turn is an array so I've rested
  • 00:14:54 in to add product to cart and in add
  • 00:14:56 product to cart we're then duplicating
  • 00:15:00 our cart array here to not mutate the
  • 00:15:03 old array in the old state object we're
  • 00:15:06 then finding the item we want to update
  • 00:15:08 we're then doing all the updating logic
  • 00:15:10 and in the end here that's the important
  • 00:15:12 thing I will not call set cart or set
  • 00:15:15 timeout even here I will just return my
  • 00:15:18 updated cart here or to be precise since
  • 00:15:23 I returned at product to cart here and I
  • 00:15:25 need to return a new state here and we
  • 00:15:27 just the point that state should be an
  • 00:15:29 object that has a cart property what I
  • 00:15:32 actually return up here is not the
  • 00:15:34 updated cart but an object that has the
  • 00:15:36 cart property where I do actually get my
  • 00:15:41 updated card words to assign my updated
  • 00:15:44 card now if your state object has more
  • 00:15:47 data than just the cart then you maybe
  • 00:15:52 want to pass the state in to add product
  • 00:15:54 to cart so that here we get state
  • 00:15:56 instead of card so that where I want to
  • 00:15:59 use the cart I have to access state card
  • 00:16:01 like this and then down there
  • 00:16:03 I can copy all our state properties
  • 00:16:06 first and then only override the card
  • 00:16:09 property so that would be the right
  • 00:16:10 strategy if your state is an object with
  • 00:16:13 multiple properties and this reducer
  • 00:16:16 function only changes one of them so
  • 00:16:18 that you don't lose the others now we're
  • 00:16:20 returning this here in the case of add
  • 00:16:23 product to cart now when we're removing
  • 00:16:25 a product from cart I want to call
  • 00:16:27 remove product from card here and I need
  • 00:16:30 to pass in my product ID which I expect
  • 00:16:33 to get on my action let's say so action
  • 00:16:36 product ID could be what I pass on and I
  • 00:16:39 also pass on my state here as an extra
  • 00:16:41 argument because and remove product from
  • 00:16:43 cart I also expect to get that product
  • 00:16:45 ID and that state so that in there I can
  • 00:16:48 get my current cart with the same
  • 00:16:50 strategy as when we add an item to the
  • 00:16:53 cart then we do all the updating logic
  • 00:16:55 as before and as explained in part one
  • 00:16:58 of this a miniseries or off this to
  • 00:17:01 video series but then here at the bottom
  • 00:17:03 I'm also not setting anything instead
  • 00:17:06 I'm just returning a copy of the old
  • 00:17:09 state and I override the cart with the
  • 00:17:11 updated cart just like this and now with
  • 00:17:16 that we have our reducer function here
  • 00:17:18 which we export we're also exporting our
  • 00:17:20 actions up there and we can now use this
  • 00:17:23 here in global State and other parts of
  • 00:17:25 the application if we want to now let's
  • 00:17:27 use it here in global State we got our
  • 00:17:31 context here where I bind add product
  • 00:17:33 cart and remove product from cart and
  • 00:17:36 now I want to use my reducer function
  • 00:17:38 for this instead of using use state like
  • 00:17:43 this so let me comment this out I use
  • 00:17:47 userid user and I need to pass in my
  • 00:17:50 reducer function here and I will get
  • 00:17:52 something back for that now let's first
  • 00:17:54 of all import the reducer function so
  • 00:17:57 let's import something from and that
  • 00:18:01 would be the reducers file so from there
  • 00:18:04 I want to import my shop reducer and
  • 00:18:07 that shop reducer is the function I want
  • 00:18:10 to pass to use reducer here and what I
  • 00:18:14 get back is the state so the state
  • 00:18:18 managed by dad reducer we could also
  • 00:18:20 name this cart state if you want to
  • 00:18:22 remember I simply change the logic so
  • 00:18:26 that state here is actually an object
  • 00:18:28 with a card property and therefore we
  • 00:18:33 should use it as such here in global
  • 00:18:35 state and the second element we get here
  • 00:18:37 in this array returned by us reducer is
  • 00:18:39 a dispatch method which allows us to
  • 00:18:42 dispatch a new action object that
  • 00:18:44 reaches that reducer function and then
  • 00:18:47 hopefully triggers something there so
  • 00:18:49 that is that action that is received
  • 00:18:50 here by the reducer function we control
  • 00:18:53 what this is because we dispatch it now
  • 00:18:57 therefore in here in global state we can
  • 00:18:59 get rid of all their logic here in front
  • 00:19:01 of set timeout
  • 00:19:02 I will leave set timeout here though and
  • 00:19:05 the same for removing items because that
  • 00:19:07 is now handled in the reducer and now
  • 00:19:10 instead of setting the card here or
  • 00:19:11 setting some state directly I'll comment
  • 00:19:14 this out and instead now I will dispatch
  • 00:19:17 and now I need an action from a reducers
  • 00:19:20 file so we'll import both actions
  • 00:19:22 because eventually I will need both add
  • 00:19:24 product and remove product so these two
  • 00:19:27 action identifier and now we don't just
  • 00:19:30 dispatch add product instead our action
  • 00:19:33 has to be a JavaScript object because in
  • 00:19:36 our reducer function we are expecting
  • 00:19:40 that action has a type property and a
  • 00:19:42 product or in the case of removing a
  • 00:19:45 product ID property and hence in the
  • 00:19:49 global state whoops in the global state
  • 00:19:51 here I dispatch type add product that is
  • 00:19:56 our identifier we're looking for and
  • 00:19:58 then product which is the product I'm
  • 00:20:02 getting here and the same logic applies
  • 00:20:05 down there here I dispatch a JavaScript
  • 00:20:08 object with a type of remove product and
  • 00:20:11 here we need to forward our product ID
  • 00:20:14 so let's do that
  • 00:20:15 and now we just have to make sure we use
  • 00:20:19 the card state object here so in the
  • 00:20:22 place where you just used cart we use
  • 00:20:24 card state dot card now that is all and
  • 00:20:28 this should now use use reducer in
  • 00:20:30 conjunction with the
  • 00:20:31 context API so if we now go back hmm
  • 00:20:34 we get an error do you know why well we
  • 00:20:38 get an error because whilst everything I
  • 00:20:40 said is basically how it works
  • 00:20:42 we got one flaw in this application and
  • 00:20:45 that is well the moment where our app
  • 00:20:47 loads we have no initial State anymore
  • 00:20:49 right we have our product but that is
  • 00:20:51 not related to the card for the card
  • 00:20:54 here we have our reducer here and that
  • 00:20:58 essentially returns our default state
  • 00:21:02 initially and we have no default state
  • 00:21:04 here so this is just a undefined what we
  • 00:21:08 can do to fix this is in the place where
  • 00:21:11 we call use reducer you can pass in an
  • 00:21:14 initial state as a second argument and
  • 00:21:16 here this could be an object with the
  • 00:21:18 card key which is an empty array
  • 00:21:19 initially and now with that we ensure
  • 00:21:22 that this is not undefined but actually
  • 00:21:24 an object that fits our card state
  • 00:21:27 structure and therefore now this reloads
  • 00:21:29 and it works and if I go to products and
  • 00:21:32 they start adding items you see this
  • 00:21:35 updates correctly here we see our items
  • 00:21:38 here in the cart and we can also remove
  • 00:21:40 them from there with that slight delay
  • 00:21:42 added by set timeout and this is how you
  • 00:21:45 can use the context API with hooks with
  • 00:21:48 either use state as I did it in the
  • 00:21:51 first part of this video or with the
  • 00:21:53 help of use reducer to get even closer
  • 00:21:55 to that redux experience where you also
  • 00:21:58 add reducer functions of course and
  • 00:22:00 ultimately it's of course up to you
  • 00:22:02 which style you prefer the advantage of
  • 00:22:05 the reducer approach of course is that
  • 00:22:07 you can have more complex state changing
  • 00:22:10 logic that you outsource so that you
  • 00:22:12 don't bloat your component too much here
  • 00:22:14 but ultimately that is up to you of
  • 00:22:16 course the same restrictions and
  • 00:22:19 thoughts as I shared them at the end of
  • 00:22:22 the first video still apply so should
  • 00:22:25 you use context API over redux have a
  • 00:22:28 look at my first video and there at my
  • 00:22:30 summary at the end to hear my thoughts
  • 00:22:32 on that that does not change by the fact
  • 00:22:34 that you are now using hooks because the
  • 00:22:36 reasons I named there still are all
  • 00:22:38 there they're not eradicated by hooks I
  • 00:22:41 hope this was helpful and I hope I see
  • 00:22:43 you in our videos too
  • 00:22:45 by