0:00
Hello everyone, in today's video we're going to create a react application that has infinite
0:05
scrolling built into it and we're going to actually use quite a few interesting features
0:09
of react such as refs and creating our own custom hooks. It's going to be a ton of fun
0:14
so let's get started now. Welcome back to web dev simplified, my name is Kyle and my job is to simplify the web for you
0:25
so if that sounds interesting make sure you subscribe to my channel for more videos just
0:28
like this one. And to get started on the left hand side I have visual studio code open and all I did
0:34
was run npx create react app with a period and this generated all of the boilerplate react code
0:41
that we're going to need. I then went around and removed all the files we don't need such as css
0:46
files and such as service workers and test files so that we're just left with our index.js and an
0:52
empty app file to actually put all of our react code inside of. Also on the right I have the
0:57
finished version of our application all we do is search here for some form of book that we want
1:01
this is going to query a book api so we can type in test for example and you can see we get a bunch
1:06
of different results and when we get to the bottom of the page you see it says loading right there
1:10
and then it'll load in our results and we can just continually scroll down until we get to the very
1:14
end of all of our results. This will just infinitely scroll until there's no more books with the name
1:18
test inside of their title. Also something this will do is if we scroll all the way back to the
1:23
top and do a different search for example if we search for te you see it'll reset our pagination
1:28
so that we start all over with our infinite scroll again from the beginning and it'll clear out all
1:32
of our old search results for us. So in order to get started let's actually just create the basic
1:37
jsx template for our application and we can do that inside of our app here. The first thing that
1:42
we're going to have is we're going to have an input component so let's create an input
1:46
this is going to be a type of text and this is actually going to be here this input right here
1:51
after that we're going to return some other stuff so let's put this inside of fragments
1:55
so we can return the multiple sets of html inside of our jsx there we go in here is where we're
2:00
going to put our books they're going to be in a div and then we're just going to have the title
2:04
let's just copy that down a couple times so that we have an example of what this is going to look
2:08
like and then lastly we're going to have a div which is going to contain loading for when we're
2:13
loading and if we have an error we're going to have a div which is going to contain error
2:17
so let's save that and actually start our application just run npm run start and if we
2:24
let that run for just a little bit you see it's going to start up over on the side here and there you go you can see that we have our boilerplate html being generated for us and now
2:32
we can actually work on implementing the api and infinite scrolling inside of our application and
2:38
to do that we're going to use axios to call our api so let's install axios by typing in npm i
2:44
followed by axios and this will install the library axios for us which works a lot like the
2:49
fetch library does but it's much easier to use rather than fetch which can be a bit difficult
2:53
to use and isn't supported in all browsers now that we have that done we could do all of our
2:59
code inside of our app for rendering our books actually querying them using axios and doing all
3:05
that other stuff but with react and hooks it's a lot easier to break out our logic into a custom
3:10
hook so we're going to create a custom hook called use book search dot js and inside of here
3:17
we're going to create a functional component and if you have the extension installed which is called
3:21
es7 react redux graphql blah blah snippets you can just type rfc and hit enter and it will generate
3:29
all the boilerplate code for you for a function component in react which is also the same boiler
3:33
plate code for actually generating a custom hook we can just remove the jsx because we're not
3:38
actually going to return jsx and we also no longer need to import react but we do want to import
3:44
use effect just like that which is going to be the hook that we're going to be using inside of
3:49
our custom hook because we want to call our api and that's an aside effect that we want to call
3:54
every single time some parameters change we also want to make sure that we have the use state hook
4:00
because we're going to be storing state inside of this custom hook which we then expose outside of
4:05
this custom hook by returning it down here in this return for now we'll just return null and
4:10
inside of this component what we need to do is query that api like i mentioned but we need to
4:15
take in a few parameters for what we're querying the first thing we want to take in is the query
4:20
essentially whatever we type into this text box here we want to send to our search we also want
4:24
to send in here what page number we're on because while we are doing infinite scrolling the way that
4:30
infinite scrolling works is as soon as you get to the bottom of your list of results it'll query
4:35
the next page of results from the api and append it onto the bottom of our page so we need to know
4:40
what page number we're currently trying to get from the api and once we have those two pieces
4:45
of information we can actually set up axios inside of an effect so we can just say here use effect
4:51
and this is going to take a function which is what we're going to do every single time that
4:56
the parameters inside of this array which is the second argument to use effect change and in our
5:01
case we want this to happen every time our query or our page number change and inside of here we
5:08
can use axios to get our information let's first import axios from axios and we just want to say
5:15
axios and this is just a function which is going to take all the parameters that we need to give
5:20
it so for example a method this is going to be a get request we also need to give it a url i'm
5:25
just going to copy this url from the api we're using essentially this is the open library api
5:31
and we're using the search api that they have built in which has pagination built into it so
5:35
we can pass that as our params so the first thing is our query and in this case they call that param
5:41
q in the api so we're going to pass q as query and we also need to pass page which is just our
5:47
page number variable and these are things you can find in the api documentation for this api but for
5:53
our example q is our query and page is the page number that we're on inside of our query also with
5:59
axios this is going to be a promise based thing so we can just use dot ven and this first dot ven
6:05
here is going to return us our response so we have our response variable and then we can actually do
6:10
something with that response and the data of our response is instead of res dot data and this data
6:16
we can just console.log this for now and we will take a look at what this looks like so that we
6:21
can actually understand what our data is doing so now back inside of our app let's import that hook
6:26
so we can say import use book search we want to import that from our dot slash use book search
6:35
so now we actually have this hook inside of our application and we can actually use this hook for
6:40
example we want to pass it our query and we want to pass it our page number which right now we don't
6:44
have stored anywhere so we need to store these inside of state so up here we can just import that
6:50
use state hook which will allow us to create some state and the first thing we can do is set up state
6:56
for our query so we have query and set query and we're going to set this here equal to use state
7:02
and by default our query is just going to be an empty string and this use state function returns
7:07
a query as well as a function to set that query which is going to re-render our application
7:12
let's copy this down and do the same thing for our page number as well as for setting our page
7:19
number and by default we're just going to have page number one for when we load our application
7:25
now we actually have these variables being set but we're not updating them anywhere the easy
7:30
one to update is our query so we can just update that in our input we can set an on change event
7:35
to this and we can just say in here we want to call a function called handle search and we can
7:40
create that function just like this so we'll say handle search and this is going to take the event
7:47
and then it's going to actually do some stuff with that vent which is going to be setting our query
7:51
so we can say set query and we want to set that to e dot target dot value and this is just going
7:57
to be the value of whatever is in the search box we also want to set our page number back to one
8:02
because every time we query we want our data results to start at the very first page we don't
8:07
want them to start at page seven if we do a new query now that that's all done let's actually
8:13
save this inspect our page over here and make sure this is working as we expect if we go into
8:17
the console zoom this in a little bit you can see that we first are getting an object for our blank
8:22
result but if we query test we should here you see get a bunch of results and this is because we're
8:27
not canceling our previous request so every character that we type is actually sending off
8:32
a request because every character we type causes this function to run which updates our query which
8:37
then updates our search so we're going to fix that in a little bit but what i do want to look at is
8:41
the actual results we get returned as you can see this data has a docs field which is all of the
8:47
different books that we are querying you can see it's a very long list and inside of here we also
8:52
have the number of results found as well as the start so this is what page we're currently on
8:56
so for example start zero is page one and then whatever our pagination number is which in this
9:02
case i think is 100 so start 100 would be page number two and this num found is going to be
9:07
really useful for when we know when to end our infinite scrolling but to get started let's worry
9:12
about this cancellation because we don't want to send a query every single time we want to cancel
9:17
our old query if we are typing information so in our use book search here axios has a really easy
9:23
way to set up cancellations it takes a parameter called cancel token and this cancel token is equal
9:29
to axios whoops axios dot cancel token and we want to create a new one of these and this is actually
9:37
capital cancel here and inside of this cancel token it's just going to take a function and
9:41
this function takes a single parameter which is going to be the cancel token and we want to set
9:47
our own variable to that so let's create a variable called cancel and now we're going to be setting
9:52
that variable cancel here to the c from our cancel token essentially this is allowing us to cancel
9:57
our request and what we can do inside of use effect is if you return something from use effect
10:02
you return a function and then inside that function we can just call cancel and this is going to
10:08
cancel our request every single time it recalls a use effect so now let's save that and we can
10:13
start typing inside of here and you can see that immediately we're getting uncaught promises
10:17
because every single time that we cancel something it causes a error to occur inside of our promise
10:23
so let's actually catch that we can say inside of here we want to catch our error and what we want
10:28
to do is we want to check to see if this is an axios cancellation error so luckily inside of axios
10:33
they have built in a really easy way to say is cancel we just pass it our error and if so we just
10:39
want to return essentially we're saying ignore every single time that we cancel the request
10:44
because we meant to cancel it now if we save that and go over here and start typing you'll see that
10:49
only one request is made no matter how many characters we type it's only going to send that
10:53
one single request and it's not going to send a bunch of extra requests because it's actually
10:58
canceling those for us which is really great also inside of this catch we can return any errors we
11:03
get so we can actually notify the user of errors so now let's start setting up the state inside of
11:09
our use book search so we can return this because right now this does nothing it's just returning
11:13
null and logging to the console but we actually want to return data to our user from this so we
11:19
can set up our state and we're going to have a few different things to state the first thing we're
11:22
going to have is loading so we want a state for loading as well as a way to set loading and we
11:28
can say use state and of course by default we're going to set our loading to true because the very
11:33
first thing we're going to do is load inside of our application next we're going to set up a error
11:39
as well as set error and by default we're not going to have an error so we're just going to
11:43
set that to false the last two pieces of state is we're going to have our state for our books this
11:48
is pretty self-explanatory this is the actual books we get back from our api call and we want
11:53
this to be an empty array to start with because we have no books being found and then the little
11:59
bit more confusing piece of state we want is a has more as well as set has more and we're going to
12:06
set this here to use state and by default we're just going to set that to false just in case there
12:11
is no more results and this has more essentially is telling us when we get to that num found as
12:17
you can see there's 79 000 results if we somehow scroll through all those we don't want to keep
12:21
making requests to the api because there is no more results so this will essentially prevent us
12:26
from making requests that we don't really want to make now that we have all of our state set up
12:30
let's actually start setting the state inside of our application so the first thing we want to do
12:35
is every time we make a request we want to set loading to be true because we are now loading
12:40
and we want to set our error to be false because we no longer have an error because we're starting
12:44
a brand new request which hopefully will succeed instead of fail also inside of our dot then here
12:50
we can set our state for our books so we can say set books and this is actually going to be using
12:56
the function version of setting state which allows us to take the previous state which in our case is
13:00
previous books so we can actually modify our previous books and what we want to do is we want
13:05
to return our previous books combined with our new books so we can say previous books and we want to
13:11
spread that as well as we want to add in here our new books which is going to be our res.data.docs
13:18
as you can see over here we have if I open this up docs this is going to be our books
13:22
and we want to map over that because we just want the title of our books so we're going to have a
13:26
book here we just want to get b.title because this is the title of our book and we don't care about
13:32
anything else one thing to note though is that this actually is going to return to us a list of
13:38
variables where we could have multiple titles because this data actually does things beyond
13:42
just the title of the book it actually has additions and other things so it may have
13:46
multiple titles that are exactly the same and we want to remove those and a really easy way to do
13:51
that is which what's called a set essentially a set in javascript you can pass it an array
13:56
and it's going to return just unique values it's not going to return any of the other information
14:01
so we're going to have only the unique titles and all we need to do is convert this back to an array
14:06
by just spreading over our entire set it's a little bit confusing but essentially what we're doing is
14:11
we're combining our old books with our new books and then we're converting it to a set so we remove
14:16
all the duplicates and then we're converting it back to an array so we can do all of our normal
14:20
array manipulation such as looping and mapping that we want to do later so now that we have our
14:25
book set let's also set our has more and has more is going to be pretty simple we just want to check
14:31
here if the res.data whoops.data.docs.length is greater than zero which essentially means
14:41
that we have no more data because there's no books returned to us so we know that we never
14:45
need to make this query again also we can set our loading here to false because we're no longer
14:50
loading our data also instead of our catch we want to set our error to be true because we actually do
14:56
have an error that is coming from our results something went wrong with our api so we want to
15:00
make sure we make note of that and now that we have all these different variables being set
15:05
we can actually return these to our user so down here in our return we can return an object and
15:10
this object is going to contain loading it's going to contain error books as well as has more
15:16
so now all the state from our hook is being returned from this hook and we can use it inside
15:21
of our app so now here we can say that we want a constant variable it's going to be equal to this
15:27
and we're just going to destructure out our books our has more we're going to destructure out loading
15:32
as well as error so now we have all the different variables from our used book search that we have
15:38
available inside of our application and we can actually start rendering some of this information
15:42
for example we can come in here and actually render out our books so we can say books dot map
15:47
so we can loop over all of our books and for each book what we want to do is we just want to call a
15:53
function here and this function is just going to print out a div so we can just say return
16:00
div we need to give it a key because in react whenever you render a list of items from an array
16:06
you need a unique key which in our case we're just going to put our book in there because these
16:09
are the titles of our book and we know they're unique since we use that set property to make
16:14
sure we only returned unique books and then we can just put the name of the book in here as well
16:19
also our loading we can put in if we are loading then we want to put the text of loading dot dot
16:25
dot and let's just copy this down exact same thing for error but instead we're going to say error
16:32
and we can remove this error down here and now if we save this close out of this console and we just
16:37
type in test for example you can see it says loading and then we get the list of all of our
16:40
different books but you will notice our books aren't quite being structured right and that's
16:45
because in our used book search i forgot to actually spread out our results so now if we
16:50
save this and do a new search for test you should see we're getting all of our results perfectly in
16:55
here but of course there's no pagination set up yet but that's okay we're going to work on that now
17:00
but there's still one bug if we scroll back up to the top and let's say we change our query
17:05
to be just t or t there we go and now you can see that our results aren't actually being appended
17:10
onto the end we have all of our t results as well as our test results so what we need to do inside
17:15
of our use book search is use another effect and this effect is going to be a very small effect
17:21
and all it's going to do is every single time that we change our query we're going to set here our
17:27
books so we're going to say set books we just want to set it back to an empty array so every
17:32
time we change our query we're going to reset our book array so that we don't have any of our old
17:36
books being shown up now if we save this search for test you see we get all of our test results
17:42
if we change it to t you can see we're just going to get our t results back which is perfect
17:46
now with all that out of the way we can finally work on the part that you've been waiting for
17:50
which is setting up pagination so if we go back to our app here one thing that i forgot to do
17:55
is set our value here whoops a value to our query so let's just make sure we do that that way just
18:01
in case we change our query somewhere else it'll be updated inside of this input field also to set
18:06
up pagination we need to use what are called refs inside of react so we can just come in here and
18:11
say use whoops ref and a ref is essentially a value that persists after each render because
18:18
inside of react every single thing that we do is only stored inside that render unless it's part
18:23
of our state but if we want to store something between renders that isn't part of our state we
18:28
need to use ref and ref is really great when you need to store references to elements for example
18:33
if we want to get a reference to our books element our input element or if you want to get a reference
18:38
to something that's related to the document api and in our case we're using intersection observer
18:43
which is part of the document api so let's create a variable i'm just going to do it all the way up
18:48
here we're going to say const observer is going to be equal to use ref just like that and by default
18:56
the first time this gets ran it's just going to have undefined as the value which is okay we also
19:01
need to get a reference to the very last book element because what we're going to do is we're
19:05
going to make it so that when we scroll all the way down and that our very last book element in
19:10
our case scandinavian cooking is shown on the screen then we actually want to change our page
19:15
number and add one to it so intersection observer is going to allow us to say when something's on
19:20
our screen but we need to get an element reference to that very last element in our books array in
19:26
order to know which element is the last one and you would think we could just do this with use ref
19:31
but like i mentioned use ref is not part of our state so it doesn't update every single time that
19:36
it changes so when our reference changes it doesn't actually rerun our component so what we're going
19:42
to use is use callback and this actually has a really unique interaction with use ref where if
19:47
we set a ref for example if we said ref is equal to and we used a use ref so if i just come up here
19:54
and say const last book element ref and i set it equal to use callback and i actually supplied
20:02
information here we could set that reference down here and now what's going to happen is whenever
20:07
this element is created it's going to call the function instead of use callback with the reference
20:13
to the element that we're using down here so it's going to call a function which is going to have
20:17
node for example as the parameter and this node corresponds to this individual element right here
20:23
but we only want to do this for our very last book so let's put in a simple if check here and
20:28
we can just say that if our books dot length is equal to index plus one and we can just use the
20:36
index inside of our map like this we can say index it's the second property to map so if for
20:41
example our length of our books is equal to our index plus one essentially this is the very last
20:46
book then what we want to do is we want to return this because we want to get reference to that very
20:51
last book otherwise if it's not the last book we want to do almost the exact same thing but we no
20:57
longer need this ref anymore so we can just return a normal div just like that now if i come up here
21:04
and i just want to put in something that says console.log node so we can see that this is
21:09
working and if we save this search for test and if i inspect our page you can see that that last
21:15
book which is scandinavian cooking is getting logged out to us so it's calling this last book
21:20
element ref every single time we render that component now all we have left to do is actually
21:25
set up our intersection observer in here so that we know when we're on our last element so the
21:29
first thing we can do is we can say if loading so we want to check if we are loading then we just
21:34
want to return because if we're loading our information we don't want to trigger our infinite
21:38
scrolling because otherwise it'll just constantly call the api while we're loading and we definitely
21:43
don't want that so with that out of the way next thing we need to do is we want to check to see if
21:48
we have an observer so the way refs work is they have a variable property called current which is
21:54
whatever the current iteration of that variable is so if we have an observer what we want to do
21:59
is we want to disconnect that observer because we're going to reconnect it so we can say observer
22:03
dot current dot disconnect call that function this is going to disconnect our observer from
22:10
the previous element so that way our new last element will be hooked up correctly and we just
22:14
make sure we check our observer because as you remember by default this is going to be null the
22:19
very first time around the next thing we need to do is set our current observer so we can say
22:24
observer dot current is going to be equal to a new intersection observer and this is going to
22:30
take in a function and this function takes all the entries that are available so everything that it's
22:35
watching is going to be in this entries array as soon as they become visible we're going to implement
22:40
this function in just a little bit but we want to finish out our function here by saying if we have
22:45
a node so for example if something is actually our last element we just want to make sure that our
22:50
observer is observing it so we can get our current observer and we can say that we want to observe
22:54
our node just like that and just like all the other callbacks that you get with hooks we actually need
23:00
to return a list of dependencies in our case our dependencies are going to be loading as well as
23:06
has more these are our only two dependencies that we're going to be messing with and now inside of
23:10
this function we can implement this so the first thing we want to check is if entries and we want
23:15
to get our first entry because we're only ever observing one single node so if our node that
23:20
we're observing we want to say if it's intersecting essentially that means it's on the page somewhere
23:25
then we're just going to console dot log visible so we can actually see if this is working and of
23:31
course we're getting an error and that's just because this hook down here which is actually
23:36
getting our loading information i need to just move this up so that it's above the code that's
23:41
actually using that loading so we'll just put it all the way at the top here and now if we save
23:45
this that error should go away perfect now we can type in test and you can see nothing's getting
23:50
logged but as soon as we get to the very bottom of our page and that element becomes visible you'll
23:54
see it gets logged out that it's visible which is exactly what we want and inside of this code we can
23:59
actually add one to our page so we can just say set page number whoops set page number we want to
24:07
get our previous page number and all we want to do is add one to that so we'll say previous page
24:14
number plus one just like that and also the last thing we need to check inside of here is if we
24:20
have more so the reason we're checking has more is if we're out of elements to query essentially
24:25
our api has queried all the data we don't want to continually keep calling this api so this won't
24:30
allow us to keep paginating forever because otherwise it would just paginate forever so now
24:35
let's save this and see if this works if we type in test and we scroll all the way to the bottom of
24:40
our page you can see we get the text loading and it allows us to scroll some more and we get loading
24:44
again and if we search something that's a little bit more specific so that we can test to see if
24:49
our has more works we can say the lord of the rings and now it's loading and as soon as that's
24:55
done loading we can see that we can scroll all the way down it's loading scroll a little bit more
25:01
loading keep going you can see we got loading again and hopefully we're getting close to the
25:06
end here as you can see that's the very end the loading went away and it's not trying to keep
25:10
querying our api it just completely stopped which is exactly what we want and that's all it takes
25:16
to set up infinite scrolling with react if you enjoyed this video make sure to check out my
25:20
other videos linked over here and subscribe to the channel for more videos like this one
25:24
thank you very much for watching and have a good day