Laravel 5.5 và React JS Phần 2: Thêm sửa xoá sử dụng Laravel làm Restful API và Reactjs làm Frontend

Giới thiệu

Ở phần trước chúng ta đã tìm hiểu cách cài đặt Laravel với React Preset ở bài viết Laravel 5.5 và React JS Phần 1: Cài đặt và Hiển thị ví dụ, phần này chúng ta sẽ làm thêm sửa xoá cơ bản sử dụng Laravel và Reactjs.

Khởi động

Tạo seeder cho bảng users:

php artisan make:seeder UsersTableSeeder

Thêm đoạn code này vào run() function trong file database/seeds/UsersTableSeeder:

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

Chạy seed command chỉ định file seeder chúng ta vừa tạo:

php artisan db:seed --class=UsersTableSeeder

Cài đặt package react-route-dom để routing. Chạy command sau:

npm install --save [email protected]

Ở thời điểm viết bài phiên bản mới nhất của react-router-dom là 4.2.2. Bạn có thể xem tài liệu tại đây.

Cài đặt package history dùng để quản lý lịch sử của trình duyệt:

npm install --save history

Đây là repo của pacakage history: https://github.com/ReactTraining/history.

Để recomplile tự động mỗi khi chúng ta thay đổi script ở thư mục assets chạy command sau:

npm run watch

Restful API đơn giản sử dụng Laravel

Restful API

Đây là nội dung file app\Http\Controllers\UserController.php

<?php

namespace App\Http\Controllers;

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

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $users = User::all();
        return response()
            ->json($users);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        User::create($request->all());
        return response()
            ->json(['message' => 'Success: You have added an user']);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'The user is not exists']);
        }
        return response()
            ->json($user);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $user->update($request->all());
        return response()
            ->json(['message' => 'Success: You have updated the user']);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $user = User::find($id);
        if (! $user) {
            return response()
            ->json(['error' => 'Error: User not found']);
        }
        $user->delete();
        return response()
            ->json(['message' => 'Success: You have deleted the user']);
    }
}

Đoạn code trên implement các function với mục đích như sau:

  • index: Lấy tất cả các users để hiển thị ngoại list
  • edit: Lấy user info ra màn edit
  • update: Cập nhật thông tin user
  • destroy: Xoá user

Routes

Chúng ta sửa file routes/web.php với nội dung như sau:

<?php
Route::group(['prefix' => 'api'], function () {
    Route::resource('users', 'UserController');    
});
Route::view('/{any}', 'welcome')
    ->where('any', '.*');

Điều chúng ta cần chú ý nhất ở đây là đoạn route này cần đặt cuối file để bắt tất cả những uri khác các các request trên vào view chứa reactjs.

Route::view('/{any}', 'welcome')
    ->where('any', '.*');

Đoạn routes này thì chỉ đơn giản là nhóm các api vào 1 group có prefix là api mà thôi:

Route::group(['prefix' => 'api'], function () {
    Route::resource('users', 'UserController');    
});

Sử dụng react js làm frontend

Chỉnh sửa file view

Thay đổi file resources/views/welcome.blade.php với nội dung:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>Laravel 5.5 - ReactJS Example</title>
        <link rel="stylesheet" type="text/css" href="{{ asset('css/app.css') }}">
        <script type="text/javascript">
            window.Laravel = {!! json_encode([
                'baseUrl' => url('/'),
                'csrfToken' => csrf_token(),
            ]) !!};
        </script>
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="{{ asset('js/app.js') }}"></script>
    </body>
</html>

File view trên có thêm biến javascript window.Laravel sẽ chứa những giá trị động lấy từ Laravel sử dụng trong javascript. Chúng ta sẽ fill nội dung Reactjs vào thẻ div có id app.

Tạo sẵn các file component và sửa file app.js

Tạo sẵn các file App.js, UserList.js, UserRow.js, CreateUser.js, EditUser.js trong resources\assets\js\components:

Sửa nội dung file resources\assets\js\app.js:

// resources\assets\js\app.js

import React from 'react'
import { render } from 'react-dom'
import {
  Router,
  Route,
  Switch
} from 'react-router-dom'
import createBrowserHistory from 'history/createBrowserHistory'
import CreateUser from './components/CreateUser'
import EditUser from './components/EditUser'
import UserList from './components/UserList'

const history = createBrowserHistory()
render (
  <Router history={history}>
    <Switch>
      <Route path='/users/create' component={CreateUser} />
      <Route path='/users/edit/:id' component={EditUser} />
      <Route path='/' component={UserList} />
    </Switch>
  </Router>, document.getElementById('app'))

File app.js sử dụng Router để điều hướng các request vào các components CreateUser, EditUserUserList. function createBrowserHistory() tạo ra history object dùng để quản lý history của trình duyệt.

Tạo layout

Tạo file resources/assets/js/components/App.js với nội dung:

// resources/assets/js/components/App.js

import React, { Component } from 'react'
import {Link} from 'react-router-dom'

class App extends Component {
  render () {
    return (
      <div>
        <nav className='navbar navbar-default navbar-static-top'>
          <div className='container'>
            <div className='navbar-header'>
              <button type='button' className='navbar-toggle collapsed' data-toggle='collapse' data-target='#app-navbar-collapse' aria-expanded='false'>
                <span className='sr-only'>Toggle Navigation</span>
                <span className='icon-bar' />
                <span className='icon-bar' />
                <span className='icon-bar' />
              </button>
              <Link className='navbar-brand' to='/'>
                Laravel 5.5 - ReactJS Example
              </Link>
            </div>
            <div className='collapse navbar-collapse' id='app-navbar-collapse'>
              <ul className='nav navbar-nav'>
                <li><Link to='/users'>Users</Link></li>
                <li><Link to='/users/create'>Add User</Link></li>
              </ul>
            </div>
          </div>
        </nav>
        <div className='container'>
          {this.props.children}
        </div>
      </div>
    )
  }
}
export default App

Ở đoạn code trên có một component mới là Link được import từ react-router-dom có tác dụng tạo ra thẻ <a/>.

Chúng ta sẽ fill nội dung của trang vào {this.props.children}.

Tạo component hiển thị danh sách users

User List

Tạo file resources/assets/js/components/UserList.js với nội dung:

// resources/assets/js/components/UserList.js

import React, { Component } from 'react'
import axios from 'axios'
import { Link } from 'react-router-dom'
import App from './App'
import UserRow from './UserRow'

class UserList extends Component {
  constructor (props) {
    super(props)
    this.state = { users: '' }
  }
  componentDidMount () {
    axios.get(window.Laravel.baseUrl + '/api/users')
      .then(response => {
        this.setState({ users: response.data })
      })
      .catch(function (error) {
        console.log(error)
      })
  }
  deleteRow (key) {
    var users = [...this.state.users];
    users.splice(key, 1);
    this.setState( {users} );
  }
  fetchRows () {
    if (this.state.users instanceof Array) {
      return this.state.users.map( (object, i) => {
        return <UserRow obj={object} key={i} index={i} deleteRow={ this.deleteRow.bind(this) } />
      })
    }
  }

  render () {
    return (
      <App>
        <h1>Users</h1>
        <div className='clearfix'>
          <Link className='btn btn-success pull-right' to='/users/create'>Add User</Link>
        </div><br />
        <table className='table table-hover'>
          <thead>
            <tr>
              <td>ID</td>
              <td>Name</td>
              <td>Email</td>
              <td>Actions</td>
            </tr>
          </thead>
          <tbody>
            {this.fetchRows()}
          </tbody>
        </table>
      </App>
    )
  }
}
export default UserList

Ở đoạn code trên, trong method componentDidMount() chúng ta sử dụng axios gọi đến api api/users lấy list users. Method deleteRow() sử dụng trong component UserRow dùng để cập nhật users list sau khi delete thành công. Method fetchRows() lặp và bind data vào các UserRow.

User Row

Nội dung file resources/assets/js/components/UserRow.js:

// resources/assets/js/components/UserRow.js

import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'

class UserRow extends Component {
  constructor (props) {
    super(props)
    this.handleDelete = this.handleDelete.bind(this)
  }
  handleDelete (e) {
    e.preventDefault()
    if (!confirm('Are your sure you want to delete this item?')) {
      return false
    }
    let url = window.Laravel.baseUrl + '/api/users/' + this.props.obj.id
    axios.delete(url)
      .then(response => {
        this.props.deleteRow(this.props.index)
      })
      .catch(function (error) {
        console.log(error)
      })
  }
  render () {
    return (
      <tr>
        <td>
          {this.props.obj.id}
        </td>
        <td>
          {this.props.obj.name}
        </td>
        <td>
          {this.props.obj.email}
        </td>
        <td>
          <Link className='btn btn-primary' to={'/users/edit/' + this.props.obj.id}>Edit</Link>
        </td>
        <td>
          <button className='btn btn-danger' onClick={this.handleDelete}>Delete</button>
        </td>
      </tr>
    )
  }
}

export default UserRow

File này chỉ đổ nội dung được binding từ method fetchRows() từ component UserList và handle action delete bằng cách sử dụng axios trong method handleDelete, sau khi xoá thành công update users list bằng cách gọi this.props.deleteRow(this.props.index), bằng cách gọi như thế này method deleteRow trên component UserList cũng sẽ được gọi.

Đây là màn hình user list:

Create user

Nội dung file resources/assets/js/components/CreateUser.js:

// resources/assets/js/components/CreateUser.js

import React, { Component } from 'react'
import App from './App'
import axios from 'axios'

class CreateUser extends Component {
  constructor (props) {
    super(props)
    this.state = {
      name: '',
      email: '',
      password: ''
    }

    this.handleChangeName = this.handleChangeName.bind(this)
    this.handleChangeEmail = this.handleChangeEmail.bind(this)
    this.handleChangePassword = this.handleChangePassword.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleChangeName (e) {
    this.setState({
      name: e.target.value
    })
  }

  handleChangeEmail (e) {
    this.setState({
      email: e.target.value
    })
  }

  handleChangePassword (e) {
    this.setState({
      password: e.target.value
    })
  }

  handleSubmit (e) {
    e.preventDefault()
    let url = window.Laravel.baseUrl + '/api/users'
    const data = {
      name: this.state.name,
      email: this.state.email,
      password: this.state.password
    }
    axios.post(url, data)
      .then(response => {
        this.props.history.push('/users')
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  render () {
    return (
      <App>
        <h1>Create An User</h1>
        <form onSubmit={this.handleSubmit}>
          <div className='form-group'>
            <label htmlFor='name'>Name</label>
            <input type='text' className='form-control' id='name' placeholder='Name'
              value={this.state.name} onChange={this.handleChangeName} required />
          </div>
          <div className='form-group'>
            <label htmlFor='email'>Email</label>
            <input type='email' className='form-control' id='email' placeholder='Email'
              value={this.state.email} onChange={this.handleChangeEmail} required />
          </div>
          <div className='form-group'>
            <label htmlFor='password'>Password</label>
            <input type='password' className='form-control' id='password' placeholder='Password'
              value={this.state.password} onChange={this.handleChangePassword} required />
          </div>
          <button type='submit' className='btn btn-primary'>Add User</button>
        </form>
      </App>
    )
  }
}
export default CreateUser

Component CreateUser khá đơn giản, chỉ bao gồm các method update các state mỗi khi có sự thay đổi, và method handleSubmit() sử dụng axios gọi đến api create user api/users, khi tạo xong redirect về users list.

Màn hình create user:

Edit user

Nội dung file resources/assets/js/components/EditUser.js:

// resources/assets/js/components/EditUser.js

import React, { Component } from 'react'
import App from './App'
import axios from 'axios'

class EditUser extends Component {
  constructor (props) {
    super(props)
    console.log(props)
    this.state = {
      name: '',
      email: '',
      password: ''
    }

    this.handleChangeName = this.handleChangeName.bind(this)
    this.handleChangeEmail = this.handleChangeEmail.bind(this)
    this.handleChangePassword = this.handleChangePassword.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  componentDidMount () {
    let url = window.Laravel.baseUrl + '/api/users/' + this.props.match.params.id + '/edit'
    axios.get(url)
      .then(response => {
        this.setState(response.data)
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  handleChangeName (e) {
    this.setState({
      name: e.target.value
    })
  }

  handleChangeEmail (e) {
    this.setState({
      email: e.target.value
    })
  }

  handleChangePassword (e) {
    this.setState({
      password: e.target.value
    })
  }

  handleSubmit (e) {
    e.preventDefault()
    let url = window.Laravel.baseUrl + '/api/users/' + this.props.match.params.id
    const data = {
      name: this.state.name,
      email: this.state.email,
      password: this.state.password
    }
    axios.patch(url, data)
      .then(response => {
        this.props.history.push('/users')
      })
      .catch(function (error) {
        console.log(error)
      })
  }

  render () {
    return (
      <App>
        <h1>Edit User</h1>
        <form onSubmit={this.handleSubmit}>
          <div className='form-group'>
            <label htmlFor='name'>Name</label>
            <input type='text' className='form-control' id='name' placeholder='Name'
              value={this.state.name} onChange={this.handleChangeName} required />
          </div>
          <div className='form-group'>
            <label htmlFor='email'>Email</label>
            <input type='email' className='form-control' id='email' placeholder='Email'
              value={this.state.email} onChange={this.handleChangeEmail} required />
          </div>
          <div className='form-group'>
            <label htmlFor='password'>Password</label>
            <input type='password' className='form-control' id='password' placeholder='Password'
              value={this.state.password} onChange={this.handleChangePassword} required />
          </div>
          <button type='submit' className='btn btn-primary'>Update User</button>
        </form>
      </App>
    )
  }
}
export default EditUser

Component EditUser chỉ khác CreateUser ở method componentDidMount() có gọi đến api api/users/{id} để lấy thông tin user. Sau khi submit cũng sử dụng axios để update thông tin user và redirect tới màn users list.

Màn hình edit user đã được fill data:

Kết

Trên đây là các bước cơ bản để làm chức năng thêm sửa xoá đơn giản với Laravel và Reactjs, nếu bạn gặp khó khăn trong việc cài đặt có thể đọc bài trước: Laravel 5.5 và React JS Phần 1: Cài đặt và Hiển thị ví dụ