NextJS 13 WARNING: Easy Mistake = Infinite Loops

NextJS 13 WARNING: Easy Mistake = Infinite Loops


NextJS 13 WARNING: Easy Mistake = Infinite Loops

Code: https://github.com/jherr/next13-use-w

👉 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:   / discord  
👉 VS Code theme and font? Night Wolf [black] and JETBrains Mono

0:00 Introduction
1:41 Project Setup
3:05 React Server Components
5:10 Client Side Components
9:25 Making A Query
11:45 Making Query Client Generic
13:18 Pokemon Setup
15:45 Enabling Pokemon Detail
18:12 Conditional Use Hooks
22:24 Finishing Query Client
23:17 Outroduction

#nextjs #nextjs13


Content

0 -> check out this innocuous little nexjs 13  client-side component now you wouldn't think that  
5.82 -> it's all that terrifying but it is because in fact  it actually looks like it's working it displays  
13.56 -> data in your page but it's actually running a huge  number of requests to your server in an infinite  
21.36 -> Loop of requests so what's going on here I mean  let's take a look at this component up at the top  
28.2 -> you have use client which is next js13's way of  saying that this is a client-side component then  
34.26 -> you've got this pretty good looking fetch data  function that goes and gets the data seems like  
40.62 -> it's a logical thing to do and then we Define our  component where we use the new use hook to listen  
46.92 -> to the promise that comes out of fetch data so  how could this go wrong well I'm going to tell  
51.84 -> you about that right after the spiffy graphic  intro but before we get into that I'm sure some  
56.46 -> folks are like hey Jack you covered this exact  topic just a couple of weeks ago what's going  
61.44 -> on well a couple of things first I've seen this  code out in the wild with people recommending  
67.62 -> that this is the way to do this and when it is  in fact an infinite Loop so I want to cover it  
73.32 -> second between now and then next js13 came out  and xjs13 has support for both the client-side  
80.16 -> stuff that we looked at in the previous  video but also react server components  
83.82 -> which I'm going to cover in this video and then  third I'm coming up with a much better way of  
89.94 -> doing the promise caching and I wanted to show  that to you as well so let's get right into it
99.72 -> okay so I've got my item up here I'm going  to go and create a new next js13 application  
107.04 -> by specifying that I want a create Next Step at  latest and then I'm going to also specify the  
112.32 -> experimental app directory and that's going to go  into next 13 use warning which is of course link  
117.96 -> to in the description right down below I'm gonna  say I do want typescript and I do want eslint  
125.4 -> and I'll bring it up in vs code all right  so if you're not familiar with next js13  
130.08 -> we have a new directory called app and that's  where your pages go so why do we have a Pages  
135.9 -> directory well we have a Pages directory because  the API routes where you get one of those by  
141.72 -> default is hello route are still over in the  Pages directory apparently there may be some  
147.78 -> changes where those two go somewhere else who  knows but that's why we have a Pages directory  
154.14 -> but this app directory is where for example  our page goes so this is the current contents  
159.78 -> of this last page and we're just going to get  rid of all this cool and let's fire this up
168.84 -> and I'll click on localhost 3000 and looks good  an empty page awesome so if I go to API hello here  
177.84 -> I do get some Json data just like that  so let's use that let's uh let's make  
184.14 -> that request and this is a server component  by default all of your components in xjs13  
190.92 -> are going to be server components that run  only and exclusively on the server unless  
195.84 -> you say that it's a client component let's not  do that because we want to check out what this  
200.4 -> react server component stuff is about so we're  going to display the data from that API hello  
206.46 -> right on the page so first we gotta go request it  so we'll do a fetch and we would get API hello but  
214.98 -> this is one running on the server so we need to  make an absolute URL and so that's giving us back  
220.62 -> a promise and what we can do in react server  components is we can just await that promise
228.96 -> but of course we can't await because  this isn't in asynchronous function  
232.92 -> but here's the trick we can  just make it an async function
238.08 -> and that works now I can go get the data from that
243.36 -> but of course I have to await that as well  and now we can just put the data into our page
254.34 -> and there you have it cool right I mean  if you come out over like the PHP world  
260.46 -> this looks really good and the really cool thing  about this is if you have nested components they  
266.28 -> can also all make those async fetches now there  are some downsides to that potentially I mean  
273.36 -> you could have a component that is deeply nested  that makes a bunch of requests and that could be  
279.6 -> a performance problem it could also mean a  bunch of renders on the server which is not  
283.2 -> great so you probably still want to treat this  like get server side props and do most of your  
289.56 -> requesting at the top level and then drill or  context it down to your other components but  
296.34 -> this is really cool okay so let's start  our path towards getting into the client  
301.74 -> side now so this is a full-on react server  component it's not running on the client at all  
308.46 -> so let's turn it into a client component the  first thing we need to do is we need to go up  
313.2 -> to the top and we need to say use clamp and that  tells an xjs that this is a client-side component  
321.84 -> now I just hit save bad things will happen  because objects are not valid as react children  
328.8 -> and returning a promise returning a promise  because this is an async function so we need  
333.42 -> to take out the async that we just put  in so it has to go back to being a normal  
339.96 -> kind of react component that we've been seeing  for years now of course now we can't do this  
345.96 -> cool await stuff so we need to use the use hook so  that we can use a promise so let's bring that in  
354.72 -> and now we can do is we can  just wrap this fetch in a use
360.18 -> and of course we want to get the data  to so let's do it then to get the data
369.3 -> foreign
374.28 -> cool and let's save
377.46 -> and it looks like it's working right  it's up there but here's the rub so  
382.56 -> let's go over to our inspector and  take a look at our Network panel  
386.94 -> and look at what's going on over the network panel  we are just getting flooded with requests to the  
393.12 -> server and this is going to chew on bandwidth  like nobody's business so this is really bad  
398.52 -> so let's talk about why this is happening  well let's go take a look over the code  
402.48 -> so what's happening is that we render home  home is executing this fetch that's going  
409.5 -> off and making an asynchronous request that  returns a promise which gets given to this use  
415.68 -> and then we fall through to the Json stringify  with the data the data is currently null so it  
422.22 -> just is null and then once this fetch returns  finally through all of this Json getting back  
428.76 -> and all that it returns essentially back that Json  that we had the data so use then stores that data  
435.72 -> presumably as state within use and that forces  a re-render of home which again creates another  
444.06 -> fetch but if you're like hey Jack you're wrong  this is exactly the same fetch that we had before  
450.9 -> that's true it is exactly the same essentially  contents of the fetch it's the same process  
456.96 -> but it's not the same reference to the  promise and that's what use is looking  
462.06 -> at we're giving it a new promise and users  like okay I guess I need to rerun that and  
466.32 -> it goes off and it reruns it and gets  the data and it just a continuous loop  
470.82 -> so I've seen folks try to get around this by  creating a local function on that fetch data
480.42 -> that does basically this so
484.5 -> we're going to get the response of the fetch  so of course this needs to be an async function
491.58 -> and then we're going to return the Json
495.84 -> which is itself a promise so now  let's put in the fetch data here
502.38 -> and this is going to work right I've seen this  In Articles I've seen this in YouTube videos  
505.98 -> this works right sure sure it does all right  we'll go over here refresh boom infinite Loop  
513.3 -> no in fact when I saw this the first time I saw  this in a video I was like okay am I wrong am  
519.06 -> I crazy this should not work and I tried it  out exactly the same code like oh okay yeah  
525.24 -> it visually looks like it's working it looks  like there's good stuff on the page and there is  
530.94 -> but yeah we're just absolutely pounding on  the server at this point so what do we do  
535.98 -> well one thing we can do is we can just get the  promise and then hold it and only use it once
549.66 -> and there we go now we are just looking at that  one promise which we got from Fetch data and we  
557.28 -> get the data back and we only make the one request  to the server which is exactly what we want right  
563.88 -> and this is basically what I showed in  the previous video as to how to solve this  
568.44 -> but I actually think I want to go one better  with this so we're going to create a query client  
575.16 -> and it's going to take a name for the query as  well as a query Creator so if I don't have the  
581.58 -> query then I'm going to call this function  and it's going to create the promise for me
587.88 -> it's going to be a function that returns a  promise that will have any in it and then what  
594.24 -> I want to do is I want to create a map between  the name and the query so let's go and create a  
600.18 -> an externalized map of that of those so let's  go create an externalized map of those fetches
614.16 -> so this map is going to go from a  name which is a string to a promise
624.06 -> so when we call this we need to first check to  see is there a promise with that name already in  
629.88 -> the cache and if there isn't then we are going  to set that value in the map to the result of  
639 -> the query function that we run and then we're  just going to return the promise that we have  
645.18 -> so now we can use Query clients down here
651.3 -> by saying that we have some data hello and  then we just give it a fetcher function
662.34 -> and we will fetch
665.94 -> that localhost API hello and return the Json
675.6 -> now it's complaining because that could be  undefined and use isn't like undefined so I'm  
681.3 -> just going to put an exclamation point here and  get around that say yeah it's going to be in the  
685.14 -> map because we know right here we either have  it and we returned it or we created a new one  
692.22 -> so now let's finish up by getting rid  of that data promise and fetch data
699.54 -> let's see how we go all right cool we  have made exactly one request to hello  
705.54 -> and we have a nice generic function here  this is really cool the only way I think  
710.22 -> we can improve this is by making it actually  typescript generic and the way that we would  
714.36 -> do that is we would capture the output  of This Promise so we'd say query result
721.74 -> and then we would Define that as a generic  parameter and we would say that the output  
726.96 -> of this query client function for  that name is going to be a promise
736.74 -> with that query result and now  if we take a look at data we see  
741.42 -> that we're getting any as the type of that data
746.76 -> so we could do here is we could say that  while the fetch is going to return a promise  
751.08 -> for like let's say a message string and now we  can see over here that data now says that we have  
756.84 -> a message string and that's because we're getting  that mapping all the way through this query result  
762.36 -> we see that the fetch is returning that  promise and then we map it through to the  
769.08 -> output of that query client so we get nice type  safe behavior all the way through that query  
774.06 -> client now if you're looking at this and saying  this looks an awful lot like react query and SWR  
780 -> yeah it does you can't argue with you there  now another thing I want to do was show how  
787.38 -> to Cascade use because that's really cool this  uses the first hook that we can actually call  
792.66 -> conditionally and I wanted to show that so what  we're going to do is we're going to build a quick  
796.32 -> Pokemon page and we're going to have two different  API routes that give us different information  
801.84 -> about our Pokemon but the first thing we need  to do is bring in some images of those Pokemon  
807.84 -> so we got Bulbasaur Charmander Ivysaur  and Venusaur you know eventually I'm  
813.06 -> going to start like really knowing  these Pokemon I use them so often  
818.76 -> and then we're going to go create a data  directory and that's going to have our Pokemon  
822.96 -> Json that's going to have a list of those  Pokemon with you know some IDs or whatever
830.52 -> again all of this code available  to you on GitHub for free  
834.36 -> so the first thing we need to do is have an  endpoint that we can call to get this list  
838.74 -> of Pokemon so let's go over here to our  hello and we're just going to clone this
844.98 -> and create a new file called  Pokemon .ts and the idea of  
850.2 -> this slash API slash Pokemon route is  that it's going to return that data
857.34 -> foreign
860.88 -> import our Pokemon
865.62 -> and then we're just going to return  it so what do you put in here for the  
870 -> type of our Pokemon well we can  put on the type of the Pokemon
876.24 -> that's all you gotta do pretty easy so let's  try this out let's go over to page and then  
881.52 -> instead of calling hello we'll call it Pokemon  and we'll call this request Pokemon as well
890.64 -> so that's cool but it doesn't match  this down here what it really is is ID  
898.86 -> which is a number and then name which  is a string and it's an array of that
907.38 -> and let's rename this Pokemon
912.18 -> and then we will map through this  to create buttons for each of our  
915.54 -> Pokemon and we click on them we will  then download the data for that Pokemon
928.62 -> all right that doesn't look too bad but  let's go and make the CSS just a little  
933.24 -> bit better so I'll get rid of that and now  yeah okay so got some big buttons nice okay
942.06 -> okay so what are we gonna do when we click on that  well we're gonna call another endpoint and I would  
948 -> imagine something along the lines of localhost  API and then the ID so we need another API route  
958.08 -> so let's go copy this one and make a new API  route called bracket ID s and what this is  
967.8 -> going to do is if we call for example slash  API one it's going to put one in as a query  
973.14 -> parameter to our function so that's pretty cool  so what do we want to do we want to go and find
981.72 -> the Pokemon that matches that query ID so what  is the type of this going to be well the type  
987.42 -> of this is going to be either one of the items in  the array or undefined so how do we get the type  
995.46 -> of the one of the items in the array well you  just take any item in the array and you can get  
999.06 -> the type of that so that covers the got a Pokemon  case and then we gotta add on the undefined option
1009.92 -> not bad but apparently we have an  issue here I think Rec query ID  
1014 -> would be a string so let's cast that into a number
1020.18 -> and that looks good so now we need to  store the Pokemon that we clicked on  
1025.76 -> when we click that button and we can use  that stored Pokemon to make a subsequent  
1030.32 -> request for the data so first we need some a  state to hold the button that we clicked on
1041.18 -> and now when we do on click
1046.28 -> we can just set the selected Pokemon  to whatever the current Pokemon is
1054.86 -> but we got some typing problems and  that's because we think that the U  
1058.76 -> State should always be undefined  because we've initialized it to be  
1062.6 -> undefined and now we're saying it's going  to give you something else like a Pokemon  
1066.02 -> so we need to reuse this definition of a  Pokemon so let's go and create a type for that
1075.5 -> and then we'll reuse that down  here and also for our usage
1084.92 -> looking good and no issues great so  let's go click around see if it works  
1090.74 -> seems to all right so let's go get our  data so we're going to reuse this use here
1100.52 -> let's call this Pokemon detail
1105.86 -> and then we're going to do the thing  that we've never been able to do with  
1108.62 -> hooks this is super cool so we're going  to say if we have a selected Pokemon
1113.84 -> then do that use make that request  otherwise just set it to null  
1121.88 -> so this is a conditional request of  course it's not the right request so  
1126.68 -> the first thing we need to do is use the  ID that we have on the selected Pokemon
1137.06 -> but we also need to make a unique request  name for this because Pokemon is already  
1141.38 -> used and if we make a request to Pokemon  we're just going to get the Pokemon data  
1145.1 -> so how do I do that well one way to do that  would be to go and create an array here
1152.3 -> and then put in the Pokemon  ID and then just join it
1157.46 -> and use whatever you want I'll use a  dash there and let's see so click cool  
1163.46 -> and we can see the requests being made  awesome so let's go and put out the data
1173.42 -> okay let's go have a look
1177.86 -> all right cool and we can see that when we go  back and try and get the original values like  
1185.9 -> one two three four we don't get them again  because they're already in the cache we're  
1190.7 -> using those promises as our data cache super cool  all right let's go finish this off by making this  
1198.2 -> look all pretty the first thing we need to do  is return an image from our API so when you  
1205.1 -> get it you'll get back not just the Pokemon data  but also so let's go and first get that Pokemon
1216.86 -> and then we'll say that if we found a  Pokemon and return all the Pokemon data
1224.9 -> plus an image which is
1230.48 -> slash and then the name of the Pokemon
1236.66 -> with a jpeg and I think it's to lowercase
1243.2 -> and otherwise we'll return undefined
1250.52 -> cool but now it's telling us hey this doesn't  match a type of Pokemon zero so we can do is we  
1256.34 -> can just say hey it's going to type Pokemon  zero but it's also going to have an image
1263.84 -> and that works so we've added using the  Ampersand this object to this object and  
1271.88 -> the type of that then becomes the  fusion of those two record types  
1277.34 -> okay so that looks good so now  let's go back over here and when I  
1283.16 -> yep now we get our image awesome and all  we need to do is just go back into our page
1290.9 -> and then put it on me and then replace this with a  conditional that says if we have a Pokemon detail
1300.38 -> then put together an image or that source is  that Pokemon detail Dot image but it's like  
1310.16 -> hey we don't have an image on that so let's  go and add that to our Pokemon definition  
1316.46 -> say that we could have an image
1321.32 -> okay now this yellow underline is telling  us that in next chance they want us to use  
1326.48 -> the image component whatever you can  do with that but let's take a look  
1331.16 -> nice okay cool a very nice little client-side  Pokemon selector that uses the use hook  
1342.02 -> in a very clean way using our query client but if  we want to take it one step further I know a lot  
1348.14 -> of folks in the previous video like oh you've got  a global there with fetch map that's terrifying  
1353.96 -> that's terrible okay fine let's go and wrap  this query client in a query client creator
1371.42 -> and then we'll return the query  client that we just created  
1375.98 -> and then we'll use make query  client to instantiate a query client
1384.26 -> and now that fetch map is just within that  closure so it's no longer a global and of  
1388.7 -> course you can go and take that make query  client function take it out put into another  
1393.02 -> module import it here whatever you want to  do it's all up to you of course all of this  
1398.42 -> code is available to you on GitHub for free  in the link in the description down below  
1403.4 -> but in the meantime if you have any questions  or comments be sure to put those also in the  
1407.96 -> comments section it's also down below and of  course if you like the video hit that like  
1412.7 -> button if you really like the video hit  the Subscribe button click on that Bell  
1416.24 -> and every couple weeks you will get a new  blue color coder video see you next time

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