NextJS 13 changes the state management game. We now have to support two different ways to get state for React Server Components (RSC) and the traditional client components.
0:00 Introduction 0:46 Example Application Setup 1:26 Setting Up The Backend 2:26 Application Anatomy 4:50 Adding Zustand 6:07 Store Initialization On Server 7:05 Using The Store In An RSC 8:38 Using The Store On The Client 10:04 Initializing The Store On The Client 12:55 Finishing The Example 13:58 Outroduction
#nextjs #nextjs13 #rsc
Content
0.2 -> If you're a fan of this channel,
you're probably also a fan of NextJS 13
4.333 -> like I am.
5.4 -> In particular the app directory
and React Server Components.
9.266 -> It's really cool stuff.
11.1 -> But I'm also a fan of Material UI,
and when I tried to connect the two,
16.266 -> I got this ominous error warning about how
19.733 -> you can't create context
in a React Server Component.
23.4 -> Well yeah, no duh.
25.2 -> But I didn't actually try.
26.4 -> All I try to do is put a button on a page.
29.033 -> So what's that about?
29.933 -> And come to think of it,
30.8 -> a lot of our current
crop of state managers use context.
34.833 -> So do those work with NextJS 13 React
Server Components in the app directory.
39.5 -> Well, I did a little digging
and this is what I found out.
45.266 -> So to investigate this a
46.366 -> little bit further, I created an example
eCommerce application.
49.633 -> It's very simple.
50.5 -> There's a header,
there is a product image.
53.633 -> In this case, it's a pizza slicer.
55.5 -> If you haven't gotten a pizza slicer,
these are awesome if you like pizza.
59.466 -> On the right hand side,
there's the description
62.566 -> and then there's the product name,
the price and and “add to cart”.
66.066 -> You click on the “add to cart”
and it will add
68.333 -> that value to the total,
which you can then clear.
71.966 -> So this is actually a combination
73.933 -> of the client side
components and React Server Components.
77.833 -> Oh, and by the way, if you're curious, I'm
79.633 -> using Tailwind for this and that actually
does work with NextJS 13.
83.466 -> So let's go take a look at the code.
85.233 -> So of course, as always, all of this code
is available to you in GitHub for free.
89.466 -> And when you launch it,
there's a bit of a twist.
93.066 -> So there's a data directory
95.233 -> that has the image of the cutters
as well as the product JSON.
99.566 -> So we're going to put that
as a server on port 8080.
102.866 -> We're going to use that
to drive our experience
105 -> because we want to go and simulate
a real e-commerce application
108.366 -> that makes a request to a back end
service.
110.866 -> The way that we do
that is over in the data directory.
113.533 -> I'm just going to do npx servor.
118.2 -> That's an alternative to npx server.
120.466 -> It supports CORS
and it also launches on port 8080.
123.566 -> So at this point I can go over here
to 8080 cutters and I can see my cutters.
128.566 -> Again, really awesome.
130.1 -> If you're a pizza person, get a pair of
these are great.
133.333 -> Okay, so we've got our data running,
so let's go get our app running.
136.1 -> We'll do yarn dev.
139.033 -> And then I'm going
to navigate to that port
142.466 -> and there we go. We are up and running.
144.766 -> So which parts of this are React
Server Components and which parts of this
148.166 -> do we want to be dynamic?
149.4 -> Well, I've made that visual.
152.1 -> If I can just go over here to our
154.933 -> components directory
and go to the VisualWrapper.
158.2 -> I can set my debug flag to true.
161.366 -> And this has the effect of
162.266 -> wrapping
all of our components in these big boxes.
166.466 -> Now, the React Server Components
are labeled of RSC and given their name.
170.433 -> So in this case this is the Header.
171.866 -> It is a React Server Component
173.366 -> or RSC inside of it is a client side
component called CartTotal.
178 -> Any client side component is shown
bracketed in red.
181.9 -> Now it's a little bit disingenuous.
183.533 -> They're not purely client side components.
185.466 -> They render both on the server
and on the client, just like they did
190.233 -> in NextJS 12 or if you have your NextJS
code in the pages directory.
194.933 -> So what do we want to do?
195.9 -> Well, we want to go and request the data,
which we actually already are.
200.166 -> So let's go over
and have a look at the home page.
203.2 -> And you can see that
we bring in all of our components.
205.433 -> And then we have Home defined
as an async function
209.6 -> that means that you can use
await is a standard practice
212.733 -> now and NextJS 13 React Server Components.
215.866 -> All you need to do is specify
that they are async components
218.9 -> and then they can just do as much
awaiting as you want right in line.
223.366 -> So in this case,
we are awaiting our product JSON.
225.766 -> So let's go take a look at that.
228 -> So that
228.3 -> has product JSON that's in that data file.
232.333 -> It then gets that data back
233.833 -> using the json method
and it coerces it to this schema.
238.1 -> So we've got the name of the product,
you got the description,
240.166 -> the image and the price.
241.7 -> Then down here
we have our layout again using Tailwind
245.933 -> just to make it easy and again
because that whole CSS in JS thing.
250.066 -> Okay, so we've got our data,
but we want to put it in
252.333 -> two different places in the UI
and this is kind of interesting.
255.533 -> We want to put it inside of a React Server
Component over here, product name,
258.9 -> without passing it as a property
and we want to put it in our client
262.8 -> side components over here
with the product name.
265.8 -> So there's two pieces of information
that we need in the state manager
270.866 -> when it comes to the product.
272 -> That's the product name and the product
price, which we're also going to use,
275.933 -> but we also need
and our state manager, the cart total,
278.633 -> because that's going to be dynamic.
And every time we add to the cart,
280.666 -> we just need to keep on
adding to that total.
282.466 -> So we now have three pieces of information
in the state.
284.133 -> The product name, the price
and the cart total.
287.533 -> To manage the state we are going to use
the Zustand library from Daishi Kato.
293.533 -> Now a bit of a twist here with Zustand.
295.433 -> It is not a context based library,
298.066 -> but in my investigations I'm
pretty sure that this is going to work.
302 -> Even if you use a context
based system like Redux.
305.266 -> Let's go in and Zustand.
307.666 -> To do that
I'll do “npm i” and then Zustand.
312 -> Nothing else is required.
313.133 -> It's an awesome stage management library.
315.366 -> Very popular. Let's run this again.
319.6 -> And so let's go create our store.
321.3 -> So at the top level here, I’m
going to create a new directory
324.1 -> called src.
326.733 -> And then within that
I'm going to create our store.ts. I’m
331.433 -> going to bring in the create function
from Zustand,
333.466 -> which then allow us to create a store.
337.133 -> And with that I'm going to create
339.866 -> and export a store called useStore.
343.633 -> We'll start off with our product name,
346.866 -> then we'll have our product price
349.966 -> and we'll have our cart total
352.633 -> and then that will about finish up.
355.066 -> So what Zustand has done is create
an external singleton
359.266 -> hook called useStore, and we can useStore
just like a normal hook.
364.166 -> But there's a couple other cool
things we can do with it.
366 -> So let's go and bring this into page
and figure out how
369 -> we get the data into our Zustand hook.
373.1 -> So let’s bring it in first.
376.166 -> And now the first
376.7 -> thing we want to do is take this data
and put it in this hook.
380.433 -> But we can't use this hook
like a hook here.
383.1 -> I mean, let's
try it out and see if it actually works.
387.3 -> So we refresh, we get boom
because we're trying to use a hook
393.2 -> in a React Server Component
and we need to use the “use
396.133 -> client” directive at the top of the file
398.433 -> if we want to do that,
which we want to do.
400.533 -> That's the whole point.
401.366 -> We want this
to be a React Server Component.
403.933 -> We want to be able to use the await stuff
to do all that.
406.833 -> So we need to be able to set the state
without actually using it as a hook.
410.533 -> So either way, you that is,
we use the setState function on it
414.6 -> and then we give it
the name and the price.
417.2 -> How cool is that? So let's try it out.
420 -> And that seems to work.
421.333 -> Let's go and find out if it actually works
423.366 -> by going into our ProductInfo
and populating this product name.
427.566 -> So we’ll
go over here to our ProductInfo component.
431.666 -> We’ll bring in our store.
438 -> And now we want to populate product name.
439.566 -> So how do we do that?
440.266 -> Well, when using expression
442.9 -> and what we can do is
we can just call getState
445.933 -> on that store and get out of that
the name.
448.966 -> Okay, so let's try this out.
450.433 -> So this actually works fine.
452.533 -> We get pizza cutter.
453.9 -> But we are getting some kickback
from TypeScript here, telling us
457.8 -> that it doesn't know
what the type of name is.
461.733 -> So that's not great.
462.533 -> What we've done is
we've taken a little bit of a shortcut
464.2 -> when we defined our Zustand store in that
we haven't defined a schema for it.
468.9 -> So the way we do that
is we use a generic syntax after create
472.333 -> and then give it
whatever the schema is for the store.
474.4 -> So in this case it's an object
where we have a name,
477.5 -> a price and a cart total and now
482.4 -> everybody's happy.
483.333 -> So this is the pattern that I found
to successfully use State managers
487.533 -> like Zustand in the React Server
Component style.
492.033 -> So we have our page here.
494.5 -> We are setting the state
of our external store
497.1 -> and now we don't have to drill down
the name and the price
501 -> to that ProductInfo.
501.666 -> All you need to do is give it
the description and the image.
504.666 -> Of course it's a bit arbitrary,
but you know,
506.4 -> you can imagine
that you have a deeply nested React server
509.466 -> Component
that needs to be able to get access to it.
511.066 -> You don't want to prop drill
all the way down or do anything crazy.
513.533 -> So this is actually a reasonably decent
and realistic example.
517.866 -> So now that we have the React Server
Component side done, let's do the client
521.7 -> side components that would include
add to cart and also cart total.
525.166 -> So let's go check out add to cart
and see how that's going to go.
530.1 -> Okay.
530.533 -> So currently this isn't even a client
component, it is a React Server Component.
535.566 -> The first thing we need to do is turn it
into a client component and to do that
539.033 -> we use the “use client” directive.
542.7 -> Let's see if it works.
543.666 -> And it seems to I guess.
547.4 -> What we can do is
548.633 -> we can put a console log in here
to see if it comes out on the client
552.266 -> and then we'll know
that it's coming out on the client
556.2 -> and it's to make a visible within
like two chevrons.
560.766 -> And we can see that
562.3 -> we are getting Console Ninja
telling us that we're getting “AddToCart”.
566.366 -> The little “b” here indicates
that it is coming out on the client.
570.033 -> Let's go take a look over in our Arc
572.866 -> and see in the console.
574.6 -> And we can see here that we're getting
the add to cart console.
577.866 -> Awesome.
578.333 -> So yes, we are a client side
component now.
580.4 -> So let's bring in our store.
582.1 -> And what do we need from it?
583.033 -> We need the name, the price
586.4 -> and let's put those in there.
590.033 -> And for price I'm going to use toFixed
593.033 -> and give it two.
595.2 -> That's
going to give us the .00 at the end.
597.133 -> Make it a nice US number.
599.433 -> And we'll hit save.
600.6 -> And we'll see if it works.
602.666 -> So we’re not getting the product name
and we're not getting the product price
606.1 -> and it doesn't work.
607.2 -> So what's happening here?
608.933 -> Well, what's happening
is that the state exists on the server,
612.833 -> but it doesn't
actually exist on the client.
615.233 -> So what we need is we need a component
that is a client side component
619.633 -> that's going to take the state
and initialize the store.
624.333 -> So we will create a new component
called like StoreInitializer. And
630.3 -> make it a client side component.
634.033 -> And we'll bring in our store.
636.5 -> Now we’ll create the function
for the StoreInitializer component.
639.5 -> And what's it need to take?
We need to take the name and the price.
641.766 -> Doesn’t need to take nything else,
because we don't need the description
645 -> or the image on the client.
649.833 -> And what are we going to return?
650.9 -> Well, we don't actually have any UI here
for this initializer components.
654.7 -> So we just return null.
656.4 -> And what we need to do
is we need to set the state of the store,
659.466 -> but we only want to do it once.
660.7 -> So let's go create a ref
662.233 -> so we can track if we've been called
multiple times on the client.
666.666 -> And we'll call that initialized
670 -> and we'll say if we're not initialized.
672 -> Then we're going to set the name
and the price on the store.
676.466 -> And we're going to say that we have been
initialized analysis.
678.9 -> Export that.
681 -> And we’ll bring it over here into our page
684 -> and then put it somewhere.
685.666 -> I'm going to put it right at the top here,
and that's going to initialize the store
692.1 -> on the client from the server
and it's going to work
695.833 -> because we're going to drill
down the values
698.9 -> that we want persisted in the client side
store as properties.
702.833 -> I’m going to break them out here
into name and price.
705.366 -> You could go and send a full object
if you wanted.
707.7 -> The nice thing about this
model is you actually get to decide
711.5 -> for yourself
what actually gets sent to the client.
715.4 -> With NextJS 12
we had getServerSideProps and everything
720.633 -> that came out of getServerSideProps,
whether it was relevant to the client
724.3 -> or not, would be stored as JSON
and sent to the client.
728.766 -> With NextJS 13, in this model,
we actually get to decide
733.3 -> at a granular level
what the state pieces are
737.033 -> that are important to the client,
738.933 -> which is a really nice distinction
between those two things.
741.433 -> But let's not put the cart
before the horse.
742.9 -> Let's see if it actually works.
744.533 -> Save. Go to Arc. And Tada!
747 -> It's working on the client. Awesome.
748.4 -> So now we need an add to cart,
so let's go and implement our add to cart.
753.633 -> So down here in
754.433 -> our button we need an onClick
and with that onClick
757.5 -> we're going to just call setState
and we're going to return a car total.
761.4 -> That is the current car total
plus the price.
764.566 -> Okay, looks good.
766.2 -> We're going to add the price
767.233 -> to the cart total
and hopefully that should update the cart.
771.566 -> Let's give it a try.
774.3 -> Now maybe that worked.
775.1 -> Maybe
776.733 -> it didn't. Let's go over to the cart
778.6 -> total and implement
that to see if it actually did work.
782.066 -> So once again, we will define
783.633 -> that this is going to be a client side
component by using “use client”.
787.033 -> And we'll bring in our store.
789.166 -> Now for this one,
we only need the cart total,
791.233 -> so I'm going to use a selector on our
useStore to just get out the cart total.
797.233 -> And we'll call that cart total
799.5 -> and we'll replace our current
hardcoded value with the cart total.
803.5 -> And again toFixed two to
806.166 -> give us that nice formatting.
807.6 -> Let's take a look.
809.633 -> I have clicked it a bunch of times
apparently.
811.533 -> There we go.
812.166 -> Looks good. The last thing we need to do
is implement on a clear button.
814.966 -> So let's try that.
816.2 -> And all we really need to do
is just set the cart total to zero.
821.4 -> And there you go.
822.2 -> A complete running example in NextJS 13
that shows
826.8 -> state management
on both the client and the server.
830.266 -> And you know what?
831.3 -> Honestly, this is looking a lot
like Islands Architecture to me.
836.8 -> Might be an interesting idea
for another video.
839.5 -> Suffice to say the create context error
841.8 -> provided by NextJS 13
turned out to be a little bit of a lark.
845.566 -> It was actually more about emotion,
847.733 -> which is the CSS-in-JS library
that Material-UI is based on.
852.133 -> And there's in fact a documentation
854.866 -> page around CSS-in-JS and NextJS 13
858.6 -> and how CSS-in-JS is incompatible
with React 18’s concurrency mode.
863.7 -> But the resultant error
is around createContext,
866.466 -> which is just a little bit deceptive.
868.5 -> Of course I put a link to that
documentation page in the description down
871.766 -> below as well as a link to all of the code
that you just saw in GitHub.
877.2 -> So you can try it out with your own state
manager and see how yours works.
881.3 -> In the meantime, course, hit
that like button
883.8 -> if you really like the video
and hit the subscribe button,
886.133 -> If you really, really like the video
and want to support the channel.
889.533 -> We have hit 100,000 subscribers
and I couldn't be happier.