
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