{"id":21157,"date":"2022-10-18T12:54:29","date_gmt":"2022-10-18T19:54:29","guid":{"rendered":"https:\/\/coderpad.io\/?p=21157"},"modified":"2023-06-05T14:01:19","modified_gmt":"2023-06-05T21:01:19","slug":"how-to-implement-infinite-scroll-in-react-js","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/how-to-implement-infinite-scroll-in-react-js\/","title":{"rendered":"How to Implement Infinite Scroll in React.js by Building a TikTok Clone"},"content":{"rendered":"\n<p>In 2006,&nbsp;<a href=\"https:\/\/www.gq.com\/story\/aza-raskin-interview\" target=\"_blank\" rel=\"noreferrer noopener\">Aza Raskin introduced<\/a>&nbsp;the infinite scroll concept, a functionality that would later transform our digital lives. Facebook, TikTok, AliExpress, and Pinterest, to name a few, have all implemented the infinite scroll feature in their stories or feeds.<\/p>\n\n\n\n<p>Infinite scrolling essentially brings endless information and entertainment to your screen. You keep scrolling and scrolling but never get to the end. Through the years, it has gathered a lot of criticism,&nbsp;<a target=\"_blank\" href=\"https:\/\/www.thetimes.co.uk\/article\/i-m-so-sorry-says-inventor-of-endless-online-scrolling-9lrv59mdk\" rel=\"noreferrer noopener\">even from its creator<\/a>, due to its addictive nature. Regardless, this technology is fascinating from both a behavioral and implementation perspective.<\/p>\n\n\n\n<p>This article will cover how infinite scrolling works behind the scenes and how TikTok incorporates infinite scrolling. We will also create a TikTok clone with React.js, pure CSS, and free videos from Pexels&#8217; API to understand the implementation process further.<\/p>\n\n\n\n<p>To be clear, we will mainly implement the mobile version of the TikTok feed rather than building a full-fledged TikTok clone. Here&#8217;s a preview of what our application will look like:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"684\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/10\/tiktok-1024x684.gif\" alt=\"\" class=\"wp-image-21162\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1024x684.gif 1024w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-299x200.gif 299w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-768x513.gif 768w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-18x12.gif 18w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">A cell phone screenplays short videos of people dancing, cityscapes, and wall decor. The videos change when the user scrolls up.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">How infinite scroll works<\/h2>\n\n\n\n<p>Infinite scroll implementations vary between applications, but the idea behind how they all work is mostly the same. They all rely on asynchronous programming and an API to load content.<\/p>\n\n\n\n<p>First, you have your application fetch some initial video content once the application has loaded. You also add a&nbsp;<a href=\"https:\/\/coderpad.io\/blog\/development\/addeventlistener-javascript\/\" target=\"_blank\" rel=\"noreferrer noopener\">listener<\/a>&nbsp;that watches when the user scrolls or when the video ends. When the listener is triggered, the app will asynchronously load new content via the API.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"660\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/10\/tiktok-1024x660.png\" alt=\"\" class=\"wp-image-21169\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1024x660.png 1024w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-300x193.png 300w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-768x495.png 768w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1536x990.png 1536w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-18x12.png 18w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Illustration demonstrating how infinite scrolling works, with three items loaded initially, as well as the caption &#8220;<strong>user is here<\/strong>,&#8221; which points to an arrow depicting the loading of new content from a database or API.<\/figcaption><\/figure>\n\n\n\n<p>A simple JavaScript-like pseudo code for this process might look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getContent<\/span>(<span class=\"hljs-params\">n<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ Asynchronously load 'n' new post from API<\/span>\n}\n\n<span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"DOMContentLoaded\"<\/span>, (event) =&gt; {\n  getContent(<span class=\"hljs-number\">5<\/span>);\n});\n\n<span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"scroll\"<\/span>, (e) =&gt; {\n  <span class=\"hljs-keyword\">if<\/span> (e.scrollY === somePreferredScrollFrame) {\n    getContent(<span class=\"hljs-number\">5<\/span>);\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">How TikTok works<\/h2>\n\n\n\n<p>TikTok&#8217;s feed pretty much works the same way described above; however, the content in TikTok&#8217;s case is videos created or shared by other app users. Additionally, TikTok incorporates a unique feature where each video is displayed at full height, separating them from each other, coupled with a smooth scrolling effect that makes scrolling through each video feel satisfying.<\/p>\n\n\n\n<p>Replicating special features like this one is pretty straightforward with pure CSS. We&#8217;ll utilize the scroll-snap and scroll-behavior properties for the scrolling effect. We will fully implement other methods to make the infinite scrolling effect work with native JavaScript.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to build a TikTok clone<\/h2>\n\n\n\n<p>Let&#8217;s get started by creating a new React application. To do this, make sure you have <code>npx<\/code> (<a href=\"https:\/\/nodejs.org\/en\/\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js<\/a>) installed, and run the following command:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">npx create-react-app tiktok-<span class=\"hljs-keyword\">clone<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, open the newly created projected folder in your favorite text editor, then run the following command to start the app in your browser:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">npm start<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Implement full height scroll-snap &amp; bottom navbar<\/h3>\n\n\n\n<p>Let&#8217;s proceed by creating the TikTok-like scroll-snap and bottom navigation bar components. To do this, create a new <code>components<\/code> directory in the existing <code>\/src<\/code> folder, and create two new files: <code>BottomNav.js<\/code> and <code>VideoCard.js<\/code> in this new directory. With these changes, our file tree should look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\">.\n\u251c\u2500\u2500 . . .\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">public<\/span>\n\u251c\u2500\u2500 <span class=\"hljs-selector-tag\">src<\/span>\n\u2502   \u251c\u2500\u2500 <span class=\"hljs-selector-tag\">components<\/span>\n\u2502   \u2502   \u251c\u2500\u2500 <span class=\"hljs-selector-tag\">BottomNav<\/span><span class=\"hljs-selector-class\">.js<\/span>\n\u2502   \u2502   \u2514\u2500\u2500 <span class=\"hljs-selector-tag\">VideoCard<\/span><span class=\"hljs-selector-class\">.js<\/span>\n\u2502   \u2514\u2500\u2500 . . .\n\u2514\u2500\u2500 . . .<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Inside the <code>VideoCard.js<\/code> file, paste the code below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> VideoCard = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ index }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"slider-children\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n          <span class=\"hljs-attr\">justifyContent:<\/span> \"<span class=\"hljs-attr\">center<\/span>\",\n          <span class=\"hljs-attr\">alignItems:<\/span> \"<span class=\"hljs-attr\">center<\/span>\",\n          <span class=\"hljs-attr\">display:<\/span> \"<span class=\"hljs-attr\">flex<\/span>\",\n          <span class=\"hljs-attr\">height:<\/span> \"<span class=\"hljs-attr\">100<\/span>%\",\n        }}\n      &gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Video {index}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> VideoCard;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The code above creates a new <code>VideoCard<\/code> component that accepts a single prop called <code>index<\/code>and displays this index concatenated with the word &#8220;Video.&#8221; Importantly, this component also contains the markup and class names that enable the full-height scroll snap effect.<\/p>\n\n\n\n<p>Inside the <code>BottomNav.js<\/code> file, add the following content:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> BottomNav = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">nav<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bottom-nav\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"navbar-brand\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"fa fa-home\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"navbar-brand\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"fa fa-search\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"navbar-brand\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"fa fa-plus\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"navbar-brand\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"fa fa-commenting\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"navbar-brand\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"fa fa-user\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">nav<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> BottomNav;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The code above contains the markup, class names, and icon definitions for our TikTok-like bottom navigation bar. We use&nbsp;<a target=\"_blank\" href=\"https:\/\/fontawesome.com\/\" rel=\"noreferrer noopener\">Font Awesome<\/a>&nbsp;icons for the icons, as seen in the previous code, but for it to work, we&#8217;ll also need to link its resource file. To do so, open the <code>public\/index.html<\/code> file and paste the following into the head section:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span>\n  <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"stylesheet\"<\/span>\n  <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.2.0\/css\/all.min.css\"<\/span>\n\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To sort all CSS related codes, replace the content of the default <code>src\/index.css<\/code> file with the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\">* {\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">box-sizing<\/span>: border-box;\n}\n\n*<span class=\"hljs-selector-pseudo\">::-webkit-scrollbar<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: none;\n}\n\n<span class=\"hljs-selector-tag\">html<\/span>,\n<span class=\"hljs-selector-tag\">body<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">100vh<\/span>;\n  <span class=\"hljs-attribute\">overflow<\/span>: hidden;\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n  <span class=\"hljs-attribute\">font-family<\/span>: <span class=\"hljs-string\">'Helvetica Neue'<\/span>, sans-serif;\n}\n\n<span class=\"hljs-selector-class\">.slider-container<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">100vh<\/span>;\n  <span class=\"hljs-attribute\">overflow-y<\/span>: scroll;\n  <span class=\"hljs-attribute\">scroll-snap-type<\/span>: y mandatory;\n  <span class=\"hljs-attribute\">scroll-behavior<\/span>: smooth;\n}\n\n<span class=\"hljs-selector-class\">.slider-children<\/span> {\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">100vh<\/span>;\n  <span class=\"hljs-attribute\">scroll-snap-align<\/span>: start;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#000<\/span>;\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">1px<\/span> solid transparent;\n}\n\n<span class=\"hljs-selector-class\">.video<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: absolute;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">100%<\/span>;\n  <span class=\"hljs-attribute\">height<\/span>: <span class=\"hljs-number\">100%<\/span>;\n  <span class=\"hljs-attribute\">object-fit<\/span>: cover;\n}\n\n<span class=\"hljs-selector-class\">.video-content<\/span> {\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">10px<\/span>;\n  <span class=\"hljs-attribute\">position<\/span>: relative;\n  <span class=\"hljs-attribute\">top<\/span>: <span class=\"hljs-number\">85%<\/span>;\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.bottom-nav<\/span> {\n  <span class=\"hljs-attribute\">position<\/span>: fixed;\n  <span class=\"hljs-attribute\">right<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">bottom<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">left<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">justify-content<\/span>: space-between;\n  <span class=\"hljs-attribute\">align-items<\/span>: center;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">20px<\/span>;\n  <span class=\"hljs-attribute\">background-color<\/span>: <span class=\"hljs-number\">#000<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.bottom-nav<\/span> <span class=\"hljs-selector-tag\">a<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n  <span class=\"hljs-attribute\">text-decoration<\/span>: none;\n}\n\n<span class=\"hljs-selector-class\">.fa<\/span> {\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">20px<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.fa-plus<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#000<\/span>;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">3px<\/span> <span class=\"hljs-number\">10px<\/span>;\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-number\">10px<\/span>;\n  <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">2px<\/span> solid <span class=\"hljs-number\">#ff5722c4<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As you might have noticed, this CSS code contains the styling for achieving the smooth scrolling effect, fixing our navbar to the bottom of the page, a video element we&#8217;ll add in the future, and additional styling.<\/p>\n\n\n\n<p>Finally, to wrap up this section, let&#8217;s import the newly created components in our entry file. Open <code>src\/App.js<\/code> and replace its code with the below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState, useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> BottomNav <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/BottomNav\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> VideoCard <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/VideoCard\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;videos, setvideos] = useState(&#91;]);\n\n  <span class=\"hljs-keyword\">const<\/span> getVideos = <span class=\"hljs-function\">(<span class=\"hljs-params\">length<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">let<\/span> newVideos = <span class=\"hljs-built_in\">Array<\/span>.from(<span class=\"hljs-built_in\">Array<\/span>(length).keys());\n    setvideos(<span class=\"hljs-function\">(<span class=\"hljs-params\">oldVideos<\/span>) =&gt;<\/span> &#91;...oldVideos, ...newVideos]);\n  };\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    getVideos(<span class=\"hljs-number\">3<\/span>);\n  }, &#91;]);\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"slider-container\"<\/span>&gt;<\/span>\n        {videos.length &gt; 0 ? (\n          <span class=\"hljs-tag\">&lt;&gt;<\/span>\n            {videos.map((video, id) =&gt; (\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">VideoCard<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{id}<\/span> <span class=\"hljs-attr\">index<\/span>=<span class=\"hljs-string\">{id<\/span> + <span class=\"hljs-attr\">1<\/span>} \/&gt;<\/span>\n            ))}\n          <span class=\"hljs-tag\">&lt;\/&gt;<\/span>\n        ) : (\n          <span class=\"hljs-tag\">&lt;&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Nothing to show here<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n        )}\n      &lt;<span class=\"hljs-regexp\">\/div&gt;\n\n      &lt;BottomNav \/<\/span>&gt;\n    &lt;<span class=\"hljs-regexp\">\/main&gt;\n  );\n}\n\nexport default App;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we imported the <code>BottomNav<\/code> and <code>VideoCard<\/code> components we created earlier. We also defined a <code>videos<\/code> state using&nbsp;<a target=\"_blank\" href=\"https:\/\/coderpad.io\/blog\/development\/global-state-management-react\/\" rel=\"noreferrer noopener\">React&#8217;s useState<\/a>&nbsp;and set its initial value to an empty array. In addition, we defined a <code>getVideos()<\/code> function, which receives a length, creates an array of that length and pushes the resulting values to our previously defined <code>videos<\/code> state.<\/p>\n\n\n\n<p>Furthermore, we used the&nbsp;<a target=\"_blank\" href=\"https:\/\/coderpad.io\/blog\/development\/rules-of-reacts-useeffect\/\" rel=\"noreferrer noopener\">useEffect hook<\/a>&nbsp;to call the <code>getVideos()<\/code> function once our app was mounted, causing it to add three new items to our <code>videos<\/code> state. And in our markup, we looped over <code>videos<\/code>, rendering the <code>VideoCard<\/code> component for each iteration while also passing the iteration&#8217;s id (index) as the index prop for our <code>VideoCard<\/code> component.<\/p>\n\n\n\n<p>If we preview our application at this point, we should see the following output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"684\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/10\/tiktok-1-1024x684.gif\" alt=\"\" class=\"wp-image-21193\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1-1024x684.gif 1024w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1-299x200.gif 299w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1-768x513.gif 768w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-1-18x12.gif 18w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">A cell phone screen demonstrates the user scrolling through three different text blocks, each of which is full height and contains white text on a black background.<\/figcaption><\/figure>\n\n\n\n<p>Suppose your application renders six pieces of content instead of three after completing these steps. In that case, this is due to React&nbsp;<a target=\"_blank\" href=\"https:\/\/reactjs.org\/docs\/strict-mode.html#identifying-unsafe-lifecycles\" rel=\"noreferrer noopener\">StrictMode<\/a>&nbsp;rendering the components twice because we&#8217;re in development mode \u2013 one of the impacts of&nbsp;<a target=\"_blank\" href=\"https:\/\/coderpad.io\/blog\/development\/why-react-18-broke-your-app\/\" rel=\"noreferrer noopener\">upgrading to React 18<\/a>.<\/p>\n\n\n\n<p>To fix this, open <code>src\/index.js<\/code> and remove the StrictMode option. Instead of returning:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">root.render(\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">React.StrictMode<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">App<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">React.StrictMode<\/span>&gt;<\/span>\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You change it to:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">root.render(\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">App<\/span> \/&gt;<\/span>\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And everything should work as expected.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Add infinite scroll&nbsp;<\/h3>\n\n\n\n<p>For our infinite scroll implementation, we will use the native&nbsp;<a target=\"_blank\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\" rel=\"noreferrer noopener\">Intersection Observer<\/a>&nbsp;API, which allows us to asynchronously observe the visibility of an element in our browser&#8217;s viewport.<\/p>\n\n\n\n<p>Our approach to achieving the infinite scroll will be in the following steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a custom <code>is in viewport<\/code> hook (via the Intersection Observer API) to check if an element is currently in the viewport.<\/li>\n\n\n\n<li>Pass a preferred length to the <code>VideoCard<\/code> component at which we intend to load new content. For example: after the user scrolls to 3\/5 of the screen (in this case, length is 3), load x new content.<\/li>\n\n\n\n<li>Check if we&#8217;ve scrolled to the preferred length defined in step 2 using the is in the viewport and&nbsp;<a target=\"_blank\" href=\"https:\/\/reactjs.org\/docs\/refs-and-the-dom.html\" rel=\"noreferrer noopener\">React Ref<\/a>&nbsp;functions.<\/li>\n\n\n\n<li>Using the condition from the previous step, load new x content and increase the preferred length to a future length we haven&#8217;t yet scrolled to.<\/li>\n<\/ol>\n\n\n\n<p>To accomplish the first step, create a new <code>useIsInViewport.js<\/code> file in the existing <code>\/src<\/code> directory and paste the following code into it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useEffect, useState, useMemo } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">useIsInViewport<\/span>(<span class=\"hljs-params\">ref<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;isIntersecting, setIsIntersecting] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> observer = useMemo(\n    <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span>\n      <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(<span class=\"hljs-function\">(<span class=\"hljs-params\">&#91;entry]<\/span>) =&gt;<\/span>\n        setIsIntersecting(entry.isIntersecting)\n      ),\n    &#91;]\n  );\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    observer.observe(ref.current);\n\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      observer.disconnect();\n    };\n  }, &#91;ref, observer]);\n\n  <span class=\"hljs-keyword\">return<\/span> isIntersecting;\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> useIsInViewport;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here we created a custom hook, <code>useIsInViewport<\/code>, that accepts a ref as its parameter and uses the IntersectionObserver API to check to see if the element with the provided ref is currently in the viewport.<\/p>\n\n\n\n<p>For the second step, alter the <code>src\/App.js<\/code> file such that the <code>VideoCard<\/code> component adds a new props entry like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">VideoCard<\/span>\n    <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{id}<\/span>\n    <span class=\"hljs-attr\">index<\/span>=<span class=\"hljs-string\">{id<\/span> + <span class=\"hljs-attr\">1<\/span>}\n    <span class=\"hljs-attr\">lastVideoIndex<\/span>=<span class=\"hljs-string\">{videos.length<\/span> <span class=\"hljs-attr\">-<\/span> <span class=\"hljs-attr\">1<\/span>}\n    <span class=\"hljs-attr\">getVideos<\/span>=<span class=\"hljs-string\">{getVideos}<\/span>\n\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The only difference here is that we pass a new parameter, <code>lastVideoIndex<\/code>, and set its value to the length of our original videos (3) &#8211; 1. In this manner, we initially load three videos and wish to load fresh content when the user reaches two of the three videos. We also passed the <code>getVideos()<\/code> function as a parameter so we can directly call it from the <code>VideoCard<\/code> component.<\/p>\n\n\n\n<p>The final change happens in our <code>VideoCard<\/code> component file. Open <code>src\/components\/VideoCard.js<\/code> and update its code with the one below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useRef, useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> useIsInViewport <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/useIsInViewport\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> VideoCard = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ index, lastVideoIndex, getVideos }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> elementRef = useRef();\n  <span class=\"hljs-keyword\">const<\/span> isInViewport = useIsInViewport(elementRef);\n  <span class=\"hljs-keyword\">const<\/span> &#91;loadNewVidsAt, setloadNewVidsAt] = useState(lastVideoIndex);\n\n  <span class=\"hljs-keyword\">if<\/span> (isInViewport) {\n    <span class=\"hljs-keyword\">if<\/span> (loadNewVidsAt === <span class=\"hljs-built_in\">Number<\/span>(elementRef.current.id)) {\n      <span class=\"hljs-comment\">\/\/ increase loadNewVidsAt by 2<\/span>\n      setloadNewVidsAt(<span class=\"hljs-function\">(<span class=\"hljs-params\">prev<\/span>) =&gt;<\/span> prev + <span class=\"hljs-number\">2<\/span>);\n      getVideos(<span class=\"hljs-number\">3<\/span>);\n    }\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"slider-children\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n        <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{elementRef}<\/span>\n        <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">{index}<\/span>\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span>\n          <span class=\"hljs-attr\">justifyContent:<\/span> \"<span class=\"hljs-attr\">center<\/span>\",\n          <span class=\"hljs-attr\">alignItems:<\/span> \"<span class=\"hljs-attr\">center<\/span>\",\n          <span class=\"hljs-attr\">display:<\/span> \"<span class=\"hljs-attr\">flex<\/span>\",\n          <span class=\"hljs-attr\">height:<\/span> \"<span class=\"hljs-attr\">100<\/span>%\",\n        }}\n      &gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Video {index}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> VideoCard;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here&#8217;s a breakdown of what&#8217;s happening in this component: We defined a ref, <code>elementRef<\/code>, and added it to our scroll-snap div. We also gave this div a new id and set its value to match the index of current content. We then passed the <code>elementRef<\/code>to the <code>useIsInViewport<\/code> hook to check if it&#8217;s currently in viewport.<\/p>\n\n\n\n<p>Furthermore, using React&#8217;s useState, we defined a new <code>loadNewVidsAt<\/code> state and set its initial value to the <code>lastVideoIndex<\/code> prop we passed previously. We&#8217;re doing this to make updating this value more flexible, as we can&#8217;t directly alter a prop.&nbsp;<\/p>\n\n\n\n<p>Finally, we used an if-statement to check if a video is in the viewport, and if it is, we checked if the id of this current video is equal to the value of <code>loadNewVidsAt<\/code> state (i.e., the value at which we want to load new content). If this condition is met, we load three new videos using <code>getVideos(3)<\/code> and set the <code>loadNewVidsAt<\/code> state to its previous value + 2.<\/p>\n\n\n\n<p>And voila! We have successfully implemented infinite scrolling. If you run your application at this point, everything should work as expected, as shown in the image below:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"684\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/10\/tiktok-2-1024x684.gif\" alt=\"\" class=\"wp-image-21215\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-2-1024x684.gif 1024w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-2-299x200.gif 299w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-2-768x513.gif 768w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-2-18x12.gif 18w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">A cell phone screen demonstrates the user scrolling through infinite text blocks, each of which is full height and contains white text on a black background.<\/figcaption><\/figure>\n\n\n\n<p>However, thus far, we only have infinite text, not infinite videos. Let&#8217;s dive right into adding videos in the next section.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Update with sample video files<\/h3>\n\n\n\n<p>Because our TikTok clone does not support any content publishing, we will instead load videos via a third-party API. I researched for you and discovered that&nbsp;<a target=\"_blank\" href=\"http:\/\/www.pexels.com\" rel=\"noreferrer noopener\">Pexels<\/a>&nbsp;provides an accessible and excellent API for loading community videos and images. The Pexels API is flexible in that we can specify the length of the videos we want to retrieve and filter them by different categories.<\/p>\n\n\n\n<p>What you want to do now is go to&nbsp;<a target=\"_blank\" href=\"https:\/\/www.pexels.com\" rel=\"noreferrer noopener\">Pexels&#8217; homepage<\/a>&nbsp;and create a new account. Once you&#8217;ve verified your account, go to their&nbsp;<a target=\"_blank\" href=\"https:\/\/www.pexels.com\/api\/new\/\" rel=\"noreferrer noopener\">API page<\/a>&nbsp;and request a new API. After following the instructions highlighted on this page, you should instantly get your API key. Copy this and keep it in a safe place for now.<\/p>\n\n\n\n<p>Next, we want to install the Pexels library in our application. To do this, run the following command:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">npm install pexels<\/code><\/span><\/pre>\n\n\n<p>Once that&#8217;s done, open <code>src\/App.js<\/code> and replace its content with the following code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">import { useState, useEffect } from <span class=\"hljs-string\">\"react\"<\/span>;\n\nimport { createClient } from <span class=\"hljs-string\">\"pexels\"<\/span>;\n\nimport BottomNav from <span class=\"hljs-string\">\".\/components\/BottomNav\"<\/span>;\nimport VideoCard from <span class=\"hljs-string\">\".\/components\/VideoCard\"<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">App<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;videos, setvideos] = useState(&#91;]);\n  <span class=\"hljs-keyword\">const<\/span> &#91;videosLoaded, setvideosLoaded] = useState(<span class=\"hljs-keyword\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> randomQuery = () =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> queries = &#91;<span class=\"hljs-string\">\"Funny\"<\/span>, <span class=\"hljs-string\">\"Art\"<\/span>, <span class=\"hljs-string\">\"Animals\"<\/span>, <span class=\"hljs-string\">\"Coding\"<\/span>, <span class=\"hljs-string\">\"Space\"<\/span>];\n    <span class=\"hljs-keyword\">return<\/span> queries&#91;Math.floor(Math.random() * queries.length)];\n  };\n\n  <span class=\"hljs-keyword\">const<\/span> getVideos = (length) =&gt; {\n    <span class=\"hljs-comment\">\/\/ Replace with your Pexels API Key<\/span>\n    <span class=\"hljs-keyword\">const<\/span> client = createClient(<span class=\"hljs-string\">\"YOUR_PEXEL_API_KEY\"<\/span>);\n\n    <span class=\"hljs-keyword\">const<\/span> query = randomQuery();\n    client.videos\n      .search({ query, per_page: length })\n      .then((result) =&gt; {\n        setvideos((oldVideos) =&gt; &#91;...oldVideos, ...result.videos]);\n        setvideosLoaded(<span class=\"hljs-keyword\">true<\/span>);\n      })\n      .<span class=\"hljs-keyword\">catch<\/span>((e) =&gt; setvideosLoaded(<span class=\"hljs-keyword\">false<\/span>));\n  };\n\n  useEffect(() =&gt; {\n    getVideos(<span class=\"hljs-number\">3<\/span>);\n  }, &#91;]);\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    &lt;main&gt;\n      &lt;div className=<span class=\"hljs-string\">\"slider-container\"<\/span>&gt;\n        {videos.length &gt; <span class=\"hljs-number\">0<\/span> ? (\n          &lt;&gt;\n            {videos.map((video, id) =&gt; (\n              &lt;VideoCard\n                key={id}\n                index={id}\n                author={video.user.name}\n                videoURL={video.video_files&#91;<span class=\"hljs-number\">0<\/span>].link}\n                authorLink={video.user.url}\n                lastVideoIndex={videos.length - <span class=\"hljs-number\">1<\/span>}\n                getVideos={getVideos}\n              \/&gt;\n            ))}\n          &lt;\/&gt;\n        ) : (\n          &lt;&gt;\n            &lt;h1&gt;Nothing to show here&lt;\/h1&gt;\n          &lt;\/&gt;\n        )}\n      &lt;\/div&gt;\n\n      &lt;BottomNav \/&gt;\n    &lt;\/main&gt;\n  );\n}\n\nexport <span class=\"hljs-keyword\">default<\/span> App;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The main modifications in this file are that we&#8217;ve updated the <code>getVideos()<\/code> function to now really load video files via the Pexels API, and we&#8217;ve also added a new <code>randomQuery()<\/code> function that generates random queries and allows us to pass the generated query to our Pexels API request.&nbsp;<\/p>\n\n\n\n<p>Aside from that, we changed the <code>VideoCard<\/code> component initialization to include information about loaded videos, such as the video author, video URL, and a link to the creator&#8217;s profile.<\/p>\n\n\n\n<p>Finally, update <code>src\/components\/VideoCard.js<\/code> and also replace this file&#8217;s content with the code below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useRef, useState, useEffect } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> useIsInViewport <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/useIsInViewport\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> VideoCard = ({\n  index,\n  author,\n  videoURL,\n  authorLink,\n  lastVideoIndex,\n  getVideos,\n}) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> video = useRef();\n  <span class=\"hljs-keyword\">const<\/span> isInViewport = useIsInViewport(video);\n  <span class=\"hljs-keyword\">const<\/span> &#91;loadNewVidsAt, setloadNewVidsAt] = useState(lastVideoIndex);\n\n  <span class=\"hljs-keyword\">if<\/span> (isInViewport) {\n    setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      video.current.play();\n    }, <span class=\"hljs-number\">1000<\/span>);\n\n    <span class=\"hljs-keyword\">if<\/span> (loadNewVidsAt === <span class=\"hljs-built_in\">Number<\/span>(video.current.id)) {\n      setloadNewVidsAt(<span class=\"hljs-function\">(<span class=\"hljs-params\">prev<\/span>) =&gt;<\/span> prev + <span class=\"hljs-number\">2<\/span>);\n      getVideos(<span class=\"hljs-number\">3<\/span>);\n    }\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> togglePlay = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">let<\/span> currentVideo = video.current;\n    <span class=\"hljs-keyword\">if<\/span> (currentVideo.paused) {\n      currentVideo.play();\n    } <span class=\"hljs-keyword\">else<\/span> {\n      currentVideo.pause();\n    }\n  };\n\n  useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span> (!isInViewport) {\n      video.current.pause();\n    }\n  }, &#91;isInViewport]);\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"slider-children\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n        <span class=\"hljs-attr\">muted<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"video\"<\/span>\n        <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">{video}<\/span>\n        <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{togglePlay}<\/span>\n        <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">{index}<\/span>\n        <span class=\"hljs-attr\">autoPlay<\/span>=<span class=\"hljs-string\">{index<\/span> === <span class=\"hljs-string\">1}<\/span>\n      &gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{videoURL}<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"video-content\"<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{togglePlay}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>@{author}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          Video by <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{authorLink}<\/span>&gt;<\/span>{author} <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span> on Pexel\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> VideoCard;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The main modifications to this file are that we&#8217;ve added a video markup and set its source to the one loaded from the Pexels API. We also used our custom <code>useIsInViewport<\/code> hook to check the video in the viewport and automatically play it. If the video isn&#8217;t playing, we use the useEffect hook to stop it from playing.<\/p>\n\n\n\n<p>Furthermore, we created a new <code>togglePlay()<\/code> function, similar to TikTok, that allows the user to pause or play a video anytime by simply clicking anywhere within the video frame.<\/p>\n\n\n\n<p>And now our TikTok clone is complete! When we run our program now, we get the following results:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"684\" src=\"https:\/\/d2h1bfu6zrdxog.cloudfront.net\/wp-content\/uploads\/2022\/10\/tiktok-3-1024x684.gif\" alt=\"\" class=\"wp-image-21231\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-3-1024x684.gif 1024w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-3-299x200.gif 299w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-3-768x513.gif 768w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/10\/tiktok-3-18x12.gif 18w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">A cell phone screenplays short videos of people dancing, cityscapes, and wall decor. The videos change when the user scrolls up.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Voila!<\/h2>\n\n\n\n<p>Throughout this article, we&#8217;ve explored how infinite scrolling works and how to implement the TikTok endless scrolling news feed by loading free videos from the Pexels API. For your convenience, the complete code for this tutorial is also hosted on&nbsp;<a href=\"https:\/\/github.com\/AsaoluElijah\/tiktok-clone\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a>&nbsp;and in the CoderPad sandbox below:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>\u2139\ufe0f Update line 17 in <code>src\/App.tsx<\/code> with your Pexels API key before running the sandbox.<\/p>\n<\/blockquote>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=231871&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>TikTok is a full-fledged program with a database, potentially microservices, and some other complex architecture. However, the fact that we were able to clone their news feed is pretty impressive, if I may say so.<\/p>\n\n\n\n<p>Special credit to the following resource for making this tutorial possible:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a target=\"_blank\" href=\"https:\/\/codepen.io\/Chokcoco\/pen\/YzPPvBV\" rel=\"noreferrer noopener\">CSS Scroll Snap Points Full Height (codepen.io)<\/a><\/li>\n\n\n\n<li><a target=\"_blank\" href=\"https:\/\/bobbyhadz.com\/blog\/react-check-if-element-in-viewport#:~:text=To%20check%20if%20an%20element,if%20the%20element%20is%20intersecting.\" rel=\"noreferrer noopener\">Check if an Element is in the Viewport in React.js | bobbyhadz<\/a><\/li>\n\n\n\n<li><a target=\"_blank\" href=\"https:\/\/codepen.io\/pablo-gallina\/pen\/VwzGYVa?editors=1100\" rel=\"noreferrer noopener\">Responsive Fullscreen Video Background With CSS (codepen.io)<\/a><\/li>\n<\/ul>\n\n\n\n<p>Thanks for reading!<\/p>\n\n\n\n<p><em>Hi, I&#8217;m Elijah, a technical writer, and software developer, actively sharing all I&#8217;ve learned through writing. Follow me on&nbsp;<\/em><a target=\"_blank\" href=\"https:\/\/twitter.com\/asaolu_elijah\" rel=\"noreferrer noopener\"><em>Twitter<\/em><\/a><em>&nbsp;if you enjoy programming tips and memes.<\/em><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Infinite scrolling essentially brings endless information and entertainment to your screen. TikTok and Instagram reels bring unlimited content using infinite scrolling. Learn how to implement infinite scroll in React.js by building a TikTok clone.<\/p>\n","protected":false},"author":1,"featured_media":21288,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[61],"keyword-cluster":[],"class_list":["post-21157","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/21157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=21157"}],"version-history":[{"count":97,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/21157\/revisions"}],"predecessor-version":[{"id":32652,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/21157\/revisions\/32652"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/21288"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=21157"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=21157"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=21157"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=21157"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=21157"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=21157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}