Did NextJS 13 Break State Management?

Did NextJS 13 Break State Management?


Did NextJS 13 Break State Management?

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.

Code: https://github.com/jherr/nextjs13-sta
NextJS Documentation: https://beta.nextjs.org/docs/styling/
Console Ninja: https://marketplace.visualstudio.com/

👉 Practical Module Federation Book: https://module-federation.myshopify.c
👉 No BS TS (The Book): https://no-bs-ts.myshopify.com/
👉 I’m a host on the React Round-Up podcast: https://devchat.tv/podcasts/react-rou
👉 Don’t forget to subscribe to this channel for more updates: https://bit.ly/2E7drfJ
👉 Discord server signup: https://discord.gg/ddMZFtTDa5
👉 VS Code theme and font? Night Wolf [black] and JETBrains Mono

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.
893 -> Thank you so much for all your support.
895.733 -> See on the next Blue Collar Coder.

Source: https://www.youtube.com/watch?v=OpMAH2hzKi8