12/08/2018, 17:29

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

Ở 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. Tạo seeder cho bảng users: php artisan make:seeder UsersTableSeeder Thêm ...

Ở 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.

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(AppUser::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 react-router-dom@4.2.2

Ở 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

Đây là nội dung file appHttpControllersUserController.php

<?php

namespace AppHttpControllers;

use IlluminateHttpRequest;
use AppUser;

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

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

    /**
     * Store a newly created resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    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 IlluminateHttpResponse
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    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  IlluminateHttpRequest  $request
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    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 IlluminateHttpResponse
     */
    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');    
});

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="awidth=device-awidth, 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 resourcesassetsjscomponents:

Sửa nội dung file resourcesassetsjsapp.js:

// resourcesassetsjsapp.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, EditUser và UserList. 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
    }
            
            
            
         
0