Create Infinite Scroll in Laravel Using React


I believe everybody is familiar with YouTube's and Facebooks's Infinite scroll. The Infinite scroll definitely helps you to improve overall look and feel of your application and helps you to avoid the need for long pagination list. But for me, it is your personal choice. If you like pagination stick with it but if you like to try something new, then go for infinite scroll. In this tutorial, I will show you how to easily setup Infinite Scrolling in your Laravel application without using any external third party library by using React, By Injecting React component in your existing, blade template. Besides React, I will be also using Axios for making HTTP API request and Moment.js for easy formatting the timestamp.  

If you are completely new to React or ES6 terminologies then I really suggest you learn the basics before continuing this tutorial as React is a little more complicated than jQuery or Vue.js. If you love the simplicity and doesn't care about complex frameworks like React then I suggest you check out my simplest version this tutorial using Vue.js by visiting the following link, 

https://shareurcodes.com/blog/create-infinite-scroll-with-filters-in-laravel-using-vuejs

Let's Start Coding

First thing is to include React in our application. In the case of Laravel, you can easily include React using Laravel Mix.

1) Setup Laravel Mix

Laravel Mix provides a fluent API for defining Webpack build steps for your Laravel application using several common CSS and JavaScript pre-processors. All you need to do is install Nodejs and NPM in your development system and run

npm install

to install all javascript dependencies in your application. By default jQuery, Bootstrap, Vuejs, Axios and Lodash are already included in this setup.  Now just run

npm run watch 

to compile your assets. Don't forget to include app.css and app.js files in the public folders to your Laravel View Layouts.

Now there is a little twist. If you are not using Vue.js in your laravel projects then you can replace the Vue.js with React by just running the following commands.

php artisan preset react 

This command will swap out the Vue scaffolding with React scaffolding. The default Mix configuration, components, and all other related files will be updated accordingly. Don't forget to run npm install and npm run watch after executing this command. Please note that this will work only in Laravel 5.5 and above.

Now if you want to include React to an existing Laravel project (5.4 and above) without removing Vue.js then you can follow below instructions to include React without disrupting your old Vue components.

Replace your webpack.mix.js file with following snippets.

let mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/assets/js/app.js', 'public/js')
   .react('resources/assets/js/react.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

As you can see that I am bundling my React components into a separate output file ie to public/js/react.js file. Don't forget to include this file in your blades where you use React components.

After modifying webpack.mix.js file install the latest version or React and ReactDOM as your development dependency by running the following command.

npm install react react-dom --save-dev

Then run,

npm run watch 

This command will install babel-preset-react for the first time and now everything is done and you successfully included  React in your Laravel project.

2) Configure your routes file

Add the following route to your web.php file

Route::get('demos/react-infinite-scroll','DemoController@viewReactInfiniteScroll');
Route::post('demos/react-infinite-scroll','DemoController@getReactInfiniteScroll');

The first route is for showing the Initial View Page and the second route is used by ajax call for fetching JSON search result from the database.

3) Setup your Controller 

Now Let's create our DemoController by running the following command.

php artisan make:controller DemoController

The complete code for DemoController is given below.

<?php

namespace App\Http\Controllers;

use Helper;
use App\Post;
use Illuminate\Http\Request;

class DemoController extends Controller
{
    /**
     * To display the show page
     *
     * @return \Illuminate\Http\Response
     */
    public function viewReactInfiniteScroll() 
    {    
        return view('demos.reactInfiniteScroll');  
    }

    /**
     * To  get post details for infinite scroll
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function getReactInfiniteScroll(Request $request) 
    {    
        $posts = Post::where('published',1)
                     ->with('tags')
                     ->orderBy('created_at', 'DESC')
                     ->limit(5)
                     ->offset($request->offset)
                     ->get();

        $posts = $posts->map(function ($post, $key) {
            return  [
                        'id' => $post['id'],
                        'title' => $post['title'],
                        'url'  => url('blog/'.$post['slug']),
                        'image' => Helper::catch_first_image($post['body']),
                        'body' => strip_tags(str_limit($post->body,350))
                    ];
        });    

        return $posts;   
    }

}

The viewReactInfiniteScroll method is used to just show our view page and getReactInfiniteScroll does the actual database querying and fetching of the result. Although you can directly return $posts after the database querying I am not doing that because in order to fetch post images I need to call an external Helper function. So I am using map method in Laravel Collection to run over the original collection and then to create a new associative array with only fields I needed. After that, I just return that collection as Laravel will automatically convert it to JSON when returning it. If you are wondering about catch_first_image custom helper function and how to create it you can refer the following Link.

Note: Both controller and routes parts will exclusively depend on your projects you are working on. They are unnecessary in this tutorial as only this they do is give some response data. I just added them only for reference purpose. 

4) Setup your Laravel View

Now let's create a demo/reactInfiniteScroll.blade.php file to include our where we inject our main React Component. The complete code of this file is given below.

@extends('main')

@section('title','Infinite Scroll in Laravel Using React - ')

@section('content')
<div  class="uk-container uk-margin"> 
    <div class="uk-grid uk-margin-top uk-flex-center"  data-ukgrid> 
        <div class="uk-width-1-1">
            <h3 class="uk-heading-line uk-text-center uk-text-bold uk-margin m-b-50">
                <span>Infinite Scroll in Laravel Using React</span>
            </h3>
        </div>
      
        <!-- Inject React Infinite Scroll Component --> 
        <div class="uk-width-3-4" id="react-infinite-scroll"></div>

        
    </div>
</div>   
@stop

Don't forget to include your bundled js file at bottom of your view file. Remember you will see some fancy class names but don't worry about it as CSS and HTML parts changes according to your requirements. I used UIKIt 3 to create ShareurCodes.

5) Create React Components

Before you create your react components, don't forget to install Moment.js and include your main class based Parent React Component in your resourses/assets/js/app.js or in the resourses/assets/js/react.js file (If you are including React alongside with Vue and want to bundle it separately) as below.

You can install Moment.js by using the following command

npm install moment --save-dev

After installing Moment.js, Include Moment.js and Main Class based Parent React Component for the Infinite scroll in your app.js file as follows.

window.moment = require('moment');

// React Components
require('./components/ReactInfiniteScroll/ReactInfiniteScroll');
 

Now Let's create our primary React Component. The complete code for the components/ReactInfiniteScroll/ReactInfiniteScroll.js file is given below.

import React from 'react';
import ReactDOM from 'react-dom';
import PostContent from './PostContent';
import PostLoader from './PostLoader';

class ReactInfiniteScroll extends React.Component {
  constructor(props) {
    super(props);

    this.state =  {
      posts: [],
      progress: false,
      completed: false,
    };

    this.infiniteScroll = this.infiniteScroll.bind(this);
  }

  componentDidMount() {
    this.getInitailState();
    window.addEventListener('scroll', this.infiniteScroll);
  }

  getInitailState() {
    this.setState(() => ({  
      progress: true,
    }));

    axios.post("/demos/react-infinite-scroll", {
      'offset': this.state.posts.length
    })
    .then((response) => {
      this.setState(() => ({  
        posts: response.data,
        progress: false,
        completed: response.data.length ? false : true
      }));
    })
    .catch((error) => {
      console.log(error);
    });
  }

  infiniteScroll() {
    if (!this.state.completed &&  !this.state.progress) {
      this.setState(() => ({  
        progress: true,
      }));

      axios.post("/demos/react-infinite-scroll", {
        'offset': this.state.posts.length
      })
      .then((response) => {
        this.setState((prevState) => ({  
          posts: prevState.posts.concat(response.data),
          progress: false,
          completed: response.data.length ? false : true
        }));
      })
      .catch((error) => {
        console.log(error);
      });
    }
  }

  render() {
    const Post = this.state.posts.map((post) => (
	  	<PostContent key={post.id} post={post} />
    ));

    return (
      <div>    
        { Post.length > 0 && Post }
        <PostLoader progress={this.state.progress} completed={this.state.completed} />
      </div> 

    )
  } 

}

if (document.getElementById('react-infinite-scroll')) {
  ReactDOM.render(<ReactInfiniteScroll />, document.getElementById('react-infinite-scroll'));
}

We fetch intail JSON data by calling the getInitailState method using axios on componentDidMount lifecycle method of React. We also register an scroll event listener at componentDidMount method so that infiniteScroll method will call every time we scroll to fetch more data from the server. I am also using PostContent a functional component for rendering the post contents which is called by map function in javascript and PostLoader component to show the loading gif image based on the value of progress and completed state.

The complete code for the components/ReactInfiniteScroll/PostContent.js file is given below.

import React from 'react';

const PostContent = (props) => (
  <div className="uk-card uk-card-default uk-card-small uk-card-body uk-border-rounded custom-border uk-margin" >
    <div className="uk-grid uk-grid-medium uk-flex uk-flex-middle" data-uk-grid>
      <div className="uk-width-1-1">
        <h4 className="uk-card-title uk-margin-remove-top uk-margin-remove-bottom">
          <a className="uk-link-reset uk-text-bold" href={props.post.url}>
            {props.post.title}
          </a>
        </h4>
        <span className="uk-article-meta">
          <span data-uk-icon="calendar"></span>
           Published at {moment(props.post.created_at).format('lll')}
        </span>
      </div>
      <div className="uk-width-1-3@s uk-width-2-6@m uk-height-1-1 uk-margin-small-top">
         <img data-src={props.post.image} alt={props.post.title } data-uk-img />
      </div>
      <div className="uk-width-2-3@s uk-width-4-6@m uk-margin-small-top">
        <p className="uk-margin-small uk-text-justify">
          {props.post.body}
        </p>
        <a data-uk-icon="icon:  arrow-right" href={props.post.url} title="Read More" 
        className="uk-button uk-button-link uk-button-small uk-text-bold">Read More</a>           
      </div>
    </div>
  </div>
);

export default PostContent;

I am using moment for formatting created at the field from laravel to a more human readable one. Since I am using UIkit 3 instead of Bootstrap you will see so many data attributes like data-src. Don't confuse, it has no relation to React. 

The complete code for the components/ReactInfiniteScroll/PostLoader.js file is given below.

import React from 'react';

const PostLoader = (props) => (
  <span>
    {
      props.progress && (<div className="uk-text-center">
        <img  src="/images/ajax-loader.gif" />
      </div>)
    }
    { props.completed && (<div className="uk-text-center">
      <h5 className="uk-text-bold" >No More Post Found!</h5>
      </div>)
    }
  </span>
);

export default PostLoader;

This component shows ajax-loader gif or no more post text based on the value of progress and completed props passed to them. 

Note: If you are using these components outside Laravel projects don't forget to globally include Axios.

The output image of the above program is given below.

Infinite Scroll in Laravel Using React

The Demo

You can demo the above application by visiting the following link.

https://shareurcodes.com/demos/react-infinite-scroll

If anybody has any suggestions or doubts or need any help comment below and I try will respond to every one of you as early as possible.


Web development
14th Oct 2018 05:22:16 AM
PHP Laravel Javascript React
15934

ShareurCodes

ShareurCodes is a code sharing site for programmers to learn, share their knowledge with younger generation.