NextJS Table of Contents Dynamic Active TOC | Code Blog [6]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome back to the next js coding blog in this video we'll implement the table of contents feature so just to demonstrate what we'll achieve today we can see that we're on a particular blog post and in the top right corner we have the table of contents and if we scroll down to the next section we can see that it shows the active state and we can also scroll down to the next area and then that will be highlighted and of course we can go back and we can also click on each individual area and then that will be highlighted for us so let's go ahead and work on that so we'll open up our project here and the first thing we'll do in our coding blog is we'll just install a package so we'll do an npm in store and this is coming from material ui so mui and we're just going to go slash lab because by default what the timeline component that we want uh it's not in the active uh stable version although it's good enough for our purposes and so we need to install the lab library here with material ui then we're going to work on two different components one is the table of contents component itself and then the second is each of these table of contents items so a content table of contents items it relates to the table of contents for the particular section and then it has all of the html or jsx in there so we're using mdx so we're going to need to use those two components when we write our markdown file so let's go ahead and we'll start off by building one of these items out so that's just this here now right now it's only got the heading and the paragraph here but you can easily type in html as a child so let's go ahead and create that so let's go to our components and we'll just create a new file here and i'm just going to start off with the table of contents item dot component dot tsx and i'm just going to do the underscore r a f c e to get the snippet there and then i'm just going to get rid of that dot there and make it a component that we're exporting so the next thing that i want to do is i just want to have an interface with some props and then declare the type which will be a react functional component so let's get that in first so this is going to be of the type functional component with the interface props and we need to import that from react so i'm just going to do a control period there to import that module from react and then each of these items is just going to have the topic name and then it's also going to have the html here or the jsx here so what we'll have is we'll have children for that so we'll have topic which will denote the title of the section and then we'll have children which is just all of the jsx so that's going to be of the type react node and that's going to come from react and then we can just use object destructuring to get the topic and the children when we consume this component and pass those in and then i'm just going to change this to a section just to have a little bit more semantic html and then it's going to have a h2 and this is where we're going to put our topic here and then underneath this we'll just have a div here and we'll give it the class of it doesn't really matter what it is let's just say foo actually i'm not even sure we need this so we'll come back to this if we need it as a hook and then we can just go ahead and we can put our children in there so that's just a basic table of contents item component uh it's quite simple so what we'll work on next is the actual table of contents itself and actually one more thing rather than using the div as a hook i'm going to use this whole section as the hook so i'm just going to put in the id here and the unique identifier will just be the topic because i'm going to make it so that the same topic doesn't repeat itself in the same article and then i'll just do two lowercase and i'll also just give this section a class name of section heading and then that should be enough for the table of contents item for now actually probably in general so the next thing i want to do is i want to create a new component for the actual table of contents itself so i'm just going to call this table of contents dot component dot tsx and once again i'll use the snippet ifce and i'll just change this to component now i'm just going to go ahead and copy some imports over so i'm going to copy these imports here these come from a material ui and as the particular timeline component which is what you see here i will be using so i just copied these imports from the documentation there and if you want you can just copy the code from the github repo i'm also going to copy this import here because we're going to have another functional component we won't need the react node this time but we will be making this a functional component now it's not going to take any props just yet but we might in the future so what i'll do here is i'll just have it empty interface for props and then i'll be able to use that generic type in the functional components uh type here looking at those props but there's nothing in there we'll come back to this when we refactor things uh in particular the i want to use this component on the code snippet page and there's a longer uh depth to the heading there so i want to pass in the margin top dynamically but we'll get to that when we refactor things and make everything responsive and all that for now i just want to focus on the actual core functionality of this so we have that so we also want to have a interface for the particular section so let's have that we'll create an interface here for the section and the section like the item where there's topic in children each section here it's going to have a topic and it's also going to be at a certain height a certain scroll height so we want to know what that certain scroll height is and we call the top of it the bounding top and the bottom of this section the bounding bottom so we'll just be interested in whether or not um bounding top what the scroll height of that is and also whether or not we've passed that or we're intersecting with a particular height and that will denote whether or not the particular section is active or not so we can just say we can have the topic which is a string bounding top which is a number and also the is active which is a boolean so like i said i want to add a margin top to this because right now we're on the angular component here even though there's this heading above it ah sorry i meant to say the angular section so if you don't have the margin top there even though it's not on the page you'll still be highlighted because it is on the page but just behind this heading so i wanted to allow some space for that there so preemptively i'm just going to have this margin top here and this is probably what we'll end up refactoring and putting in as a prop later on because on the other page will be different so we'll just have it as a variable here for now and then we'll have the margin top there so the next thing i want to do is i want to determine what the height is so let's just set a little bit of state there so we can just use the use state snippet here and we'll just call this offset y and we'll just change this to an uppercase o for the offset y and setting the offset y now initially when we come to the page we're going to have an offset of y and if we're on another page like a home page and we scroll down and then we click on this page what we want to do is actually just scroll up to the top um so that'll be the behavior i'm going for here so i just want to bring that new state hook in from react and i probably also want the use effect hook so another use state hook that i want to have here is the all the sections so each page i want to get all of the sections so let's have a variable for that and since we've got this section type here we know that it's just going to be an array of those so let's do that and right at the beginning will be an empty array because those table uh those contents items won't be detected just yet but we will hook into those uh another thing we'll just add while we're here at the top is we'll use the we'll get the custom theme by the using the use theme variable that was set up in the first video so let's just go ahead and import that from material so i might put that above the other imports just so it's not laid out nicely uh and then what we want to do when we first load the page is as we said we want to take the window the scroll high all the way up to the top so and the reason we're doing it this way is rather than passing fragments in and then clicking on them uh if you could see if you click on here i'll click on here is when you go to the page since we have this table of contents you can just easily click to where you want to go and then it makes it so that the scroll behavior from other pages to this page uh behaves nicely so that's just a design choice you could use fragments and deal with behaviour in a different way but you know we have this table of contents item we may as well make use of it so let's have a use effect hook here use effect so in the first instance when we go to the page it's not guaranteed that will be at the top of the scroll bar height so let's ensure that and we can do this by passing no parameters in the use effect so this will just happen on load or on render and we don't need to clean up anything because there's no active promises or observables or anything like that so what we can do is we may need to set the offset height to zero as well because it could be um you know you might have an offset at a different area from a different page this just ensures that will be initially set up to the top of the page when we load the page so the next thing we want to do is we want to get all of the sections get the bounding dimensions so let's go ahead and do that and then we'll go ahead and be able to set the sections that we have here and if we just open up the articles we open up this angular article this mdx file here what we're going for is we're going to have the table of contents at the top of this file here and what we'll have is we'll also have the table of contents item below that where each of the sections will uh you know relate to this so these two components they're sort of coupled together uh and i think that's okay i mean if you're building out software on a large scale you don't want to do this but we're just building a small blog out here uh it's not the biggest deal if we you know come up with these slightly hackish solutions [Music] so let's go ahead and do that so we'll have another use effect here so i'm just going to shift alt down this here and i'm just going to get rid of this contents here so this is also going to happen on page load i just wanted to distinguish it different to this effect here and that's setting the sections based on the mdx file so again you're going to have you know the table of contents component at the top and then you're going to have the table of contents items or the table of content sections just uh you know that's one section and then there's three sections in this example here so we need to get those from that page and then place them into the table of contents so let's go to our use effect here the first thing we're going to do here is we're going to get the elements which is going to be an array of html html elements so what we can do here is we can just say [Music] we can use the document query selector or and here we have this hook here for the section heading so let's go ahead and do this so and again these are separate components and they're rendered through this mdx page so there's no way to do it the standard react way uh that i'm aware of if there is please comment below so i'm just going to do it this way and it's not really a big deal anyway because we're rendering all the pages into html into static files um that's the whole idea of this static coding blog um but under like i know i do admit it's not the best practice to not do react way but uh i don't know if there's a better way let me know so i'm just going to get this here by getting the section with the class section heading here now it says that um you know there can be a bit of an issue relating to using the query selector um so what i'll do because this returns i believe like a node list or something like that we just want it in an array so we can use the array methods on it so we can just typecast it by using array from and then placing that in there and saving that and in the next section now that we have access to that so we've let's say we have our three table of contents items in here and i might actually just copy and paste the code in from what we're going for basically i'm going to copy this from the other my example but below the log header component we have something like this so this is just a bunch of lorem ipsum and you can see that we got the table of contents at the top it doesn't take any props and then each of these table of contents items we can see that as a topic it takes the whatever the topic is so this first topic's about ionic the second topic's about angular and so on and then as a children i've just placed in the html that i want there or jsx or whatever it is that we want to render there so i'm just going to go ahead and save that that's what we're aiming for so if we come back to our table of contents component we can say that okay if we can create a variable called all sections here and since this is each of these table of contents items components is rendering this section here which has the class name here and the id here what we can do is we can say for all sections for all the elements we can map through all of those all of the sections basically so each element which is of the type html element and its associated index which is a number we'll map through all of those and we'll say okay we just want to get the top and we're going to reassign it to bounding top so it stays in line with our interface here i could call it top as well but i think bounding top's more explicit and then on the individual element so this section here that contains it all for that section with that element there based on that class name we can go ahead and we can just say get bounding client rect so that gets all of the dimensions that's an object i'll return object with the top and the bottom and all that sort of stuff we're only interested in the top because when we scroll to the top well the first case is an exception but when we get to the first um to the second topic we can see that when we scroll to it it highlights that for us and the third highlights it for us so that's just based on the top of the section so with that in each iteration we can just go ahead and we can just return the topic which is just going to be the element and then getting the attribute get attribute id so we're getting this unique id which relates to the name of the topic and this is definitely going to be there because we've made sure it is and then we get the bounding top and i've renamed it to bounding top so i can just use this new es6 syntax and then is active this can be well initially if the index is equal to zero and remember we're going to the top of the page by default so the first element is going to be active and all the rest won't be active so i can just have this expression here and then after we go through this mapping here we can just go ahead and use our set sections hook and we can just go ahead and pass in all of the sections here so initially we're at the top of the page we need to set the sections based on the mdx file then we loop through all the sections that we have using this component here as a hook and then we set the state for all of those sections so the next thing we want to do is we also want to detect the scroll height and determine whether or not we should be making another section active because right now the first section is going to be active but the rest won't be when you change the scroll height but we want to change the scroll height and depending on the height of the bounding top of each of the sections we want to display the different sections being active or not so that's a candidate for the is use effect hook so let's just write one of those out use effect bring one of these in now let's see if essentially in the first case here i'm just going to delete a few things here but if the um it could be that we only have one section here and if we only have one section by default it's already going to be active on the based on the previous hook that we just created so if it's you know not more than one or it's less than or equal to one there's no real need to you know spend the extra resources detecting the scroll height so what we can say is if the sections dot length is less than or equal to one we can just return out and also save it in the case that initially there's zero um even though it gets set on render just to avoid any timing issues there but then we want to have this event a listener here so we'll have a variable and or a function and i'm just going to call it onscroll and essentially what it will do it will set the offset y value to the window dot page y offset and this will be called by adding an event listener and basically we can just say window.addeventlistener for the scroll event so we're only having one event listener here so it's not a huge drain on resources so we're just having a scroll event here on scroll and we're just referencing this function here so because we're having this event in the use effect hook we need to exit out of it so we need to clean up that effect and we can do that by having this return here so let's say you click on to a different you know you go back to the home page or something you don't want to be detecting this role event so basically when we're no longer using this use effect here we can say remove event a listener and then you can just you know put in the event type and the particular function that you're removing from and this is all happening when there's a change in sections so when you first load the page you're creating all of the sections and initially the scroll height will be y but as you scroll it's going to reset the offset y height as detecting that and essentially in the next set use effect hook we're going to have some logic that it depends on the sections and the offset height and then it'll be able to set which active section is which so essentially what happens is you come into this data here that's relating to a section and since this is active properties changing that section is changing so we want to be able to detect uh that a sections change and based on that that's why we uh references and also we need to have that as a variable anyway because we're using it um so let's create the actual the logic of what we're doing here the main logic and this is this use effect hook right here so let's just clean things up if these sections and we can copy this from above just so it's not calling this if the section's length is less than or is equal to zero there's nothing to do and that just is a safety case for the very first load if the section's length is equal to one then what we can do is we can just say sections the first section is active is equal to true and then we can just return so we're not performing any other code below this block but if there's more than one section well if there's only one section we're setting the first section to active because that's all it can be if it's more than one we'll get to the next section because then the first two if checks will fail so sections for each so each section is going to be a section and it's also going to have an index associated with it now we're going to have a couple of cases the first case is if it is the first element so if the index is equal to zero we can just go ahead and we can say well we know it's active on the first load but we need to set the section is active based on a condition and essentially we want to look at the next section so in this example here this section is active as long as we're not past the scroll height of this top of this second section so you can see when we get to this area here we're on the next element but then we scroll up we're back on this ionic element so if we're at the bounding top of the next element uh or before that we're in the first section but we also need to subtract off this uh height here um of the margin top because you can see here that when we get to here we're now on the angular section whereas if you're ignoring this heading we wouldn't actually be on it until about here or so so we just need to subtract that extra distance off so essentially the condition is for that first section if the next section then we can just do it like this index plus one you could also remove the index plus but i think this is a little more generic um if that bounding top of the next section and we know there's going to be a next section because it's past the length of one so it needs means that there's at least a length of two or more so this exists that's true if the bounding top is greater than the offset so that's just the scroll height and then we just need to plus on this margin top here so again uh i think i said subtract off i think we need to plus it i think you're subtracting it off the section or adding it to the scroll height so you can imagine right now the scroll height zero and then we want the scroll height around here uh so if it was if it was at the zero mark and then the text was here you would need to add on 100 there so i suppose that's the logic there if that's the logic for the first case then else the next case is that it's not the first element and it's all the other elements so we just need to consider what happens if it's the last element if it's the last element we can't um we don't know the bounding height of the next element so we just need to take that into consideration so we're going to have another if else in here so if the sections index plus one exists that means we're on uh between the second element and the second last element so in this case we only have three elements so all the middle all the elements that aren't the first and last section so if we're there we'll do something but if that doesn't exist it means we're on the last case and then if it's the last case what we'll do is we'll just say okay section is active if the sections index dot bounding top is less than or equal to the offset y plus the margin pot so it's similar logic but now we don't have reference to the next section so we have to use that particular section itself so we know that this is the last section here and we know the the bounding top of this section here and if that is less than or equal to the offset of the page plus the margin top then we can say that this area is where we're at or the other way around if the scroll height is within this section meaning the scroll height is greater than the cut off for that section plussing on the margin then it's true that that area is active otherwise it's not active okay so now we just need to deal with the logic of all of the in between uh sections so everything that's not the first and last section and that's true if the next section exists and it's not the first one so what we'll do we'll just copy some of this code here and then we'll modify it if the section the section is going to be active if the next section is um if that this is essentially true if it's uh greater than the offset height but it also has to be less than the next section as well so we can actually go ahead and we can just say and sections index dot bounding top is less than or equal to the offset y plus margin top i think i've got that logic all right or another way to look at it is let's let's evaluate this one uh first so if the section that we're on so in this case if we're on the second section here if the bounding top or the top of this element if that's less than or equal to uh yeah yeah that's right if the offset here if that's less than or equal to um the bounding top plus on the margin there actually i think it makes a little more sense the other way around all right sorry bear with me if the offset height is greater so you could just ignore the margin if the scroll height or the height of this thing here if this number here is greater than this height here and tack on the margin there then it will be in that section or basically everything after this point but we also need to cut it off from the next sections top there so then it will just be this one section here so to do that we take the next sections bounding top and then we just reverse it if the offset is less than uh the next section height and it's greater than the current section then it'll be in between so that's that and this just depends on the sections and the offset y value so whenever we're changing the height we want to [Music] you know reevaluate whether or not there's the sections active or not based on the sections and the offset y value okay so now we can just create the ui so what i'm going to do is i'm just going to have a div now we're going to refactor this we're going to have a mobile responsive video that's going to refactor things but just for the purposes of the functionality of this video i'm just going to position this and just fix it in a fixed position and then we'll come back to doing the responsiveness in a separate video so i'm just going to fix this up to the top and i'm just going to choose 140 and have it at the right so right zero where the width of the thing is going to be inherit so right now we've done all the logic or the hard work we just need the ui to reflect that so this is where our imports are going to come in handy we're going to use the timeline component from mui labs and then we're going to loop through all of our sections and we'll just map through those and then for each section of the type section with its corresponding index which is just a number starting from zero one two three we're going to go ahead and we're just going to return some jsx here so we're going to return the actual timeline item and we're mapping through this so we can do a we can just tack on the key here which is just going to pertain to the index and then there's going to be two scenarios essentially if the index is not equal to the section's length minus one it's seen a minus one because index starts at zero the length starts at one uh if that if that's uh if this evaluates to true what we can have is we can just say double ampersand and then this is where we'll tack on our timeline separator because basically what we're doing here is if it's not the last section put one of these separator things in but if it is the last section you don't need another one otherwise this just looks weird so if the index isn't equal to the section's length we can just go ahead and create one of these timeline separators and nested within one of these separators we actually have like the timeline dot that corresponds to it because it needs to connect the dots have the separator so we can just get this timeline dot here this is a self-closing one and i'm just going to make the color here secondary and the variant is going to equal to the section and if it's active i want it to be filled otherwise i just want it to be outlined and you can see that here a full circle there otherwise it's outlined okay and then after the dot you need the uh to close the you have the timeline separator and nested in there you have the timeline dot and then you need the timeline connector so but you could have the case that you know basically the opposite of this uh i might just copy the whole thing and then delete a few things but if the index is equal to the section's length minus one we know it's the last element so we don't need the separator and we also don't know we also don't need the connector we only need the timeline dot here uh again you could probably refactor this to make a little cleaner but again we're going to focus on another video on refactoring and another video on mobile responsiveness and they could be together or depending on how much work is in each but let's just get this logic out of the way so timeline now we're going to go into the timeline content itself so below this tag here we can have timeline content and i'm just going to have a span here and essentially in the span i'm just going to have the name of the particular topic so here we've got section topic and then i can go ahead and i can just give this an on click because you want to be able to click on each of these to be able to navigate there as well so on click we'll have that equal i will call this sort of function here now we'll take the window and then we'll just scroll to the particular section that we're scrolling to so the first arguments in the x direction such as zero as a placeholder and then we can just take the sections bounding top and then we can just go ahead and uh minus the margin top that we had from before so we're just taking that off there and then we want to set the offset for y to this number here so we'll scroll to the section um so actually clicking on it rather than scrolling to it so we click on a button and then it's programmatically scrolling to it so i'm going to actually need to just set this here i don't know if this is actually needed or not um but just to you know ensure that you definitely updated the state in react there just helps it out a little bit so okay so now that we got our on click method there we can just add a little bit of styling here and let's add some text decoration and since it's span we don't want it to have any underlines or anything like that set the text decoration to none set the color to a ternary based on the fact whether or not it if it's active or not if it is use the custom theme and we can just use the palette here and then we can just get the secondary color i'm looking at the main which is just that pink color uh otherwise we can just take the custom theme go the palette get the text and if it's primary and one more final thing that i had here is the text transform because when we're playing around with the capitalizing uh i mean with the topic we're putting it to lowercase so let's just go ahead and change that to capitalize and finally we'll just indicate that you can click on each of these spans by adding the cursor and setting that to pointer so we did all that and we haven't checked anything so let's just go ahead and run a npm run dev okay so it's telling us that it's started on the server let's go ahead and check that and fix up anything if it's not working and then do a final review and we did update the mdx file to have all these here oh okay yep that's good we can just go to the slug component that uses this so that is in um blog we're in the blog section we're going to need to do this in the learn section but we'll come to that um very minimal work basically because we're using these components here within the mdx we just need to pass them so here we have the table of contents component just scroll up to get the name of it and i can bring that in and i can also bring in the table of contents item component and it's complaining oh i need to import it of course and then save that and then we should see where we're at okay all right cool uh and i also need to get rid of some uh let's see here i'm seeing some text up here uh but we'll just ignore that um yeah so we scroll to angular section scroll back up to ionic went to this filler section okay so all that seems to be working let's see if we click on filler yep puts us there ionic there angular there we just sort of scroll around um yeah so that's the table of contents page again we'll come to delete this but essentially what we did is we created a table of contents and a table of contents item component and we put those into the page that we're using them so we can use them in the mdx file the item itself pertains to the particular section that we're writing there and it has a topic so we need to define what the topic is and then based on that topic that'll be the name of the section and then we'll just put in the jsx there as children or html or whatever you like to call it and then on this section we have this hook here so we've got the class name of section heading so we can get all of the sections that we've created based on this page here and then also the unique id for that which is based on the topic so that needs to be unique you couldn't have two things here called ionic for example or if you wanted to you call it ionic part two it's just a design choice that i've made so i will admit there is a slight coupling between the two and we've used like the query selector instead of doing the the uh strictly react way but for these purposes we're not you know designing super critical software or anything like that it's just a blog um it's fine it's it's it's it's going to it's going to be rendered into html it's not really a big deal at all um and yeah and then we have a few things that we're detecting here we're initially going to the top of the page that's another design choice i made rather than using fragments just because how easy it is to this is going to be fixed at the top so you can just click on it immediately so that's really easy to navigate to where you want to go um you know we select all of those sections and we set them we have a event listener that we are listening for based on the scroll that gets removed gets cleaned up uh when we are no longer on this page we then the main work is in this here where we're looping through all of the sections if it's the first case you know and there's only one element uh you know that can only be true so let's just verify that so to save this here i'll just reload here now we can see this is just always going to be active um you know if i change it back we'll just get back the behavior we're expecting but yeah we just handled that edge case there and we can see we're there nicely so yeah and then if you're these are the three main cases if it's not one there's more than one section which most likely will be um if it's the first element if it's less if it's you know if you're in the area below the next areas bounding top you're in that uh first section here if you're in between these the you know the top of the next section and this section for any of the middle elements or middle sections you're in that particular section and the last one if you're greater than that top of that then you're in the top so that's pretty much all there is to it and then we just built the ui using material labs using the timeline component and just displaying the logic and you know if it's active have it filled if not don't fill it and then also the ability to click on things which you know we just manually set the offset height back to what it would be and everything just works out nicely because you change the offset height it detects the change in offset y and then we'll just re-run the conditions that evaluate whether or not it is active or not so that's all i really wanted to cover in that video uh in the next videos we'll start to um well i mentioned we need to refactor things make the code base a little cleaner uh you know we need to make this mobile responsive um but i also want to work on some interesting things like you know building out some of more pages more functionality you know home page footers uh you know anything that i've missed uh just fixing up any loose ends or anything like that so if you're interested in continuing on this coding blog please subscribe to my youtube channel thanks so much for watching if you have any recommendations or tips or anything like that feel free to let me know and i'll see you in the next video cheers
Info
Channel: Jon Peppinck
Views: 90
Rating: undefined out of 5
Keywords: dynamic table of contents, html table of contents, dynamic accordion react, dynamic accordion tutorial, dynamic list react, learn react native, react native, react native app, react native 2020, react native course, react native project, react native tutorial, react native app tutorial, dynamic list html javascript, how to use css position to move elements, frontend development, javascript, react, react hooks
Id: sBY8Ikb9RmQ
Channel Id: undefined
Length: 57min 11sec (3431 seconds)
Published: Sun Dec 12 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.