Dynamic Datatable component using Laravel and React Js

Steps to create a dynamic datatable using React and Laravel. Some code snippet and some explanation is in this article. And demo application can be found here with installation instructions.

ReactLaravel
January 11, 2019

Share:

Hey Guys! I hope you guys are doing good, I am also doing good too and It’s been some months I am learning React Js. A few days back one of my brother Bijaya wrote an article about creating a dynamic datatable component using Laravel and Vue Js. Now I want to do the same implementation using Laravel and React Js. Let’s see how it goes.

Requirements:

  1. Laravel 5.5+
  2. React Js

Let’s create a Laravel Project and set environment variables and database, change the migration file of users as we are going to create a datatable of users records.

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->string('address');
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}

gist

Run the migration using the command: $ php artisan migrate which runs the migration for the project and new users table is created in our database. The user model already exists in Laravel just update the fillable fields as we are adding address field in users table.

protected $fillable = ['name', 'email', 'password', 'address'];

We will create by adding a route. So inside the routes/api.php add a new route with get request:

$this->get('users', 'UsersController@index')->name('users');

Now we need to create a controller using command php artisan make:controller UserController in the index method to fetch paginated records of users and return in JSON format. This controller is created inside app/Http/Controllers make changes followings.

<?php

namespace App\Http\Controllers;

use App\Http\Resources\UsersResource;
use App\User;
use Illuminate\Http\Request;

/**
 * Class UsersController
 * @property User user
 * @package App\Http\Controllers
 */
class UsersController extends Controller
{
    /**
     * @var User
     */
    protected $user;

    /**
     * UsersController constructor.
     *
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * List of users in Json format
     *
     * @param Request $request
     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
     */
    public function index(Request $request)
    {
        $query = $this->user->orderBy($request->column, $request->order);
        $users = $query->paginate($request->per_page ?? 5);

        return UsersResource::collection($users);
    }
}

gist

Here we can see UsersResource class is used there but we have not created yet. Laravel’s resource classes allow you to expressively and easily transform your models and model collections into JSON. So hurry up! Run command php artisan make:resource UserResource This class will be created inside app/Http/Resources. Add the following code in a toArray method that will only return id, name, email, address and created_at fields of the users from API.

return [
    'id'        =>  $this->id,
    'name'       => $this->name,
    'email'      => $this->email,
    'address'    => $this->address,
    'created_at' => Carbon::parse($this->created_at)->toDayDateTimeString(),
];

gist

Our user list API is completed but we are not adding any data to the user’s table so let’s do this too. To insert the random data of users for now we can make a change in database/factories/UserFactory.php class. The following method is useful to generate random data of users by using Faker Library and Faker is by default configured in Laravel.

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name'              => $faker->name,
        'email'             => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'address'           => $faker->address,
        'password'          => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
        'remember_token'    => str_random(10),
    ];
});

gist

And create a seeder file by using a command php artisan make:seed UsersTableSeeder which will create a file UsersTableSeeder.php inside database/seeds directory. Inside run method add the following, Which can generate 50 random users.

factory(App\User::class, 50)->create()

Let’s insert those random 50 users data by using a command $ php artisan db:seed.

Start your Laravel project in a local environment and open your app in a favorite browser like

http://yourappurl.local/api/users/?per_page=2&column=name&order=desc

You can see the following results: Result from API

Okay, cool! Backend listing API is completed, Now let’s move to the frontend part.


We are going to use React Js so first generate react preset in Laravel project by using a command $ php artisan preset react which will generate a scaffold for react starter architecture by default. Let’s create ReactDataTableApp.js file in resources/js which looks like this.

import React, { Component } from "react";
import DataTable from "./components/DataTable";

export default class ReactDataTableApp extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    const columns = ['id', 'name', 'email', 'address', 'created_at'];
    return (
      <DataTable url="/api/users" columns={columns} />
    );
  }
}

gist

We are using DataTable component in an above component, and passing props URL and columns so that it will make DataTable component to dynamic. So Let’s create a DataTable.js file inside resources/js/components which looks like this.

import React, { Component } from 'react';

export default class DataTable extends Component {
  constructor(props) {
    super(props);

    this.state = {
      entities: {
        data: [],
        meta: {
          current_page: 1,
          from: 1,
          last_page: 1,
          per_page: 5,
          to: 1,
          total: 1,
        },
      },
      first_page: 1,
      current_page: 1,
      sorted_column: this.props.columns[0],
      offset: 4,
      order: 'asc',
    };
  }

  fetchEntities() {
    let fetchUrl = `${this.props.url}/?page=${this.state.current_page}&column=${this.state.sorted_column}&order=${this.state.order}&per_page=${this.state.entities.meta.per_page}`;
    axios.get(fetchUrl)
      .then(response => {
          this.setState({ entities: response.data });
      })
      .catch(e => {
        console.error(e);
      });
  }

  changePage(pageNumber) {
    this.setState({ current_page: pageNumber }, () => {this.fetchEntities()});
  }

  columnHead(value) {
    return value.split('_').join(' ').toUpperCase()
  }

  pagesNumbers() {
    if (!this.state.entities.meta.to) {
      return [];
    }
    let from = this.state.entities.meta.current_page - this.state.offset;
    if (from < 1) {
      from = 1;
    }
    let to = from + (this.state.offset * 2);
    if (to >= this.state.entities.meta.last_page) {
      to = this.state.entities.meta.last_page;
    }
    let pagesArray = [];
    for (let page = from; page <= to; page++) {
      pagesArray.push(page);
    }
    return pagesArray;
  }

  componentDidMount() {
    this.setState({ current_page: this.state.entities.meta.current_page }, () => {this.fetchEntities()});
  }

  tableHeads() {
    let icon;
    if (this.state.order === 'asc') {
      icon = <i className="fas fa-arrow-up"></i>;
    } else {
      icon = <i className="fas fa-arrow-down"></i>;
    }
    return this.props.columns.map(column => {
      return <th className="table-head" key={column} onClick={() => this.sortByColumn(column)}>
        { this.columnHead(column) }
        { column === this.state.sorted_column && icon }
      </th>
    });
  }

  userList() {
    if (this.state.entities.data.length) {
      return this.state.entities.data.map(user => {
        return <tr key={ user.id }>
          {Object.keys(user).map(key => <td key={key}>{ user[key] }</td>)}
        </tr>
      })
    } else {
      return <tr>
        <td colSpan={this.props.columns.length} className="text-center">No Records Found.</td>
      </tr>
    }
  }

  sortByColumn(column) {
    if (column === this.state.sorted_column) {
      this.state.order === 'asc' ? this.setState({ order: 'desc', current_page: this.state.first_page }, () => {this.fetchEntities()}) : this.setState({ order: 'asc' }, () => {this.fetchEntities()});
    } else {
      this.setState({ sorted_column: column, order: 'asc', current_page: this.state.first_page }, () => {this.fetchEntities()});
    }
  }

  pageList() {
    return this.pagesNumbers().map(page => {
      return <li className={ page === this.state.entities.meta.current_page ? 'page-item active' : 'page-item' } key={page}>
        <button className="page-link" onClick={() => this.changePage(page)}>{page}</button>
      </li>
    })
  }

  render() {
    return (
      <div className="data-table">
        <table className="table table-bordered">
          <thead>
            <tr>{ this.tableHeads() }</tr>
          </thead>
          <tbody>{ this.userList() }</tbody>
        </table>
        { (this.state.entities.data && this.state.entities.data.length > 0) &&
          <nav>
            <ul className="pagination">
              <li className="page-item">
                <button className="page-link"
                  disabled={ 1 === this.state.entities.meta.current_page }
                  onClick={() => this.changePage(this.state.entities.meta.current_page - 1)}
                >
                  Previous
                </button>
              </li>
              { this.pageList() }
              <li className="page-item">
                <button className="page-link"
                  disabled={this.state.entities.meta.last_page === this.state.entities.meta.current_page}
                  onClick={() => this.changePage(this.state.entities.meta.current_page + 1)}
                >
                  Next
                </button>
              </li>
              <span style={{ marginTop: '8px' }}> &nbsp; <i>Displaying { this.state.entities.data.length } of { this.state.entities.meta.total } entries.</i></span>
            </ul>
          </nav>
        }
      </div>
    );
  }
}

gist

I want to explain a little bit about the DataTable component, In the above code, you can see we are initializing states entities data and meta information will be set on the state after fetch data from fetch API of any entity. To fetch the data from API we are using axios which is defined in the resources/js/bootstrap.js file. Entities list is available in this.state.entities.data and meta information for the pagination and API calls are in this.state.entities.meta. There is the listing of the entity’s records done with pagination. Also, we can make separate Pagination component and reuse for several tables as well.

Import the ReactDataTableApp Component in resources/js/app.js and render that component in the welcome.blade.php file at where datatable id attribute is defined in markup.

import React from 'react';
import ReactDOM from "react-dom";

require('./bootstrap');

import ReactDataTableApp from './ReactDataTableApp';

if (document.getElementById('datatable')) {
    ReactDOM.render(<ReactDataTableApp />, document.getElementById('datatable'));
}

gist

And in the welcome.blade.php just give the link of js file and css file with appropriate identifier something like this.

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Laravel</title>
    <link rel="stylesheet" href="{{ asset('css/app.css') }}"/>
  </head>
  <body>
    <div class="flex-center position-ref full-height" id="app">
      <div class="container">
        <div id="datatable"></div>
      </div>
    </div>
    <script src="{{ asset('js/app.js') }}"></script>
  </body>
</html>

gist

Now let’s run your app and see in the browser, Hope you do not forget to run $ npm i and $ npm run dev . Open your application in your favorite browser then you can see dynamic datatable in it.

Dynamic Datatable Using React and Laravel

Yes, that is all about for this article, we created a Dynamic datatable using Laravel and React Js. Here is the Github repo, If you have any queries or suggestions regarding this feel free to add on a comment section. I always love your responses. Thanks! Bye!

2024 © Madhu Sudhan Subedi.