Xây dựng ứng dụng CRUD với Laravel 7 & Reactjs – Phần 2

by Lê Thạch

Chào các bạn, ở hôm trước mình có giới thiệu đến các bạn cách tao một ứng dụng CRUD kết hợp giữa framework Laravel và Reactjs. Trong phần 1 mình đã hướng dẫn các bạn những thao tác xử lý tạo API với phần backend Laravel rồi, để tiếp nối và hoàn thiện ứng dụng thì trong hôm nay mình sẽ xử lý các phần còn lại ở phía Front-end thông qua Reactjs. Nếu các chưa xem qua bài viết lần trước thì mình khuyên các bạn nên đọc phần trước cho hiểu đã rồi mới xem qua bài viết này.

Phần 2: Làm việc với phần Front-end Reactjs

Bước 1:  Cài đặt cấu hình cho Reactjs

Đầu tiên, chúng ta cấu hình bộ khung Reactjs cho ứng dụng của mình bằng cách chạy lệnh sau đối với Laravel 7+

composer require laravel/ui
php artisan ui react

Còn trong trường hợp đối với project dùng Laravel 5.5+ ta phải dùng lệnh sau thì mới được hỗ trợ.

php artisan preset react

Sau khi chạy xong chúng ta vào > resources  >>   js , tại đó có 1 folder tên là components, đây là react component. Và 2 file là app.js và boostrap.js. Trong components thì có một file với tên Example.js

Các bạn đi theo đường dẫn resources  >>  views  >>  welcome.blade.php và xóa code mặc định của Laravel đang có trong file, sau đó chèn đoạn code bên dưới:

<!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">
        <title>CRUD React-Laravel by Blog Lê Thạch</title>
        <link href="{{asset('css/app.css')}}" rel="stylesheet" type="text/css">
    </head>
    <body>
        <div id="example"></div>
        <script src="{{asset('js/app.js')}}" ></script>
    </body>
</html>

Mở terminal và chạy lệnh:

npm run dev

Lệnh này compile tất cả assets và để bundled js vào trong public >> js  >>  app.js của Laravel.

Sau đó, các bạn gõ thêm lệnh sau để tạo server run project.

php artisan serve

 

Kết quả khi chạy thử project

Kết quả khi chạy thử project

Bước 2:  Cài đặt các dependencies liên quan đến Reactjs

Đầu tiên, mình sẽ cần cài đặt thêm React-route cho phần điều hướng ứng dụng của mình. React-router được dùng để giúp việc dẫn hướng UI đồng bộ với URL.

Trong React, chỉ có 1 file HTML được gọi. Bất cứ khi nào người dùng nhập 1 đường dẫn mới, thay vì lấy dữ liệu từ server, Router sẽ chuyển sang 1 Component khác ứng với mỗi đường dẫn mới. Nó sẽ giúp ứng dụng không cần phải load lại trang khi click vào một đường dẫn. Hay còn gọi là Single Page Application (SPA)

npm install [email protected]

Ở đây mình cài đặt react-router version 3.2 để tránh các lỗi khi sử dụng version cũ. Sau đó mình chạy thêm lệnh

npm run watch

Nó sẽ xem những thay đổi trong thư mục assets khi ta code và tự động compile lại. Mình sẽ dùng cái này để kiểm tra debug những lỗi trong quá trình code luôn cho tiện.

Bước 3:  Xây dựng các thành phần cần thiết của giao diện

File app.js

require('./bootstrap');
require('./components/Example');

Edit resources/js/components/Example.js

// resources/js/components/Example.js
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {HashRouter , Link} from 'react-router-dom';
import RouterPath from './pages/RouterPath';

export default class Example extends Component {
    render() {
        return (
            <HashRouter>
                <div className="container">
                    <div className="row">
                        <nav className="navbar navbar-expand-lg navbar-dark bg-dark fixed-top" id="mainNav">
                            <div className="container">
                                <a className="navbar-brand js-scroll-trigger" href="/">Blog Lê Thạch</a>
                                <div className="collapse navbar-collapse" id="navbarResponsive">
                                    <ul className="navbar-nav ml-auto">
                                        <li className="nav-item">
                                            <Link className="nav-link js-scroll-trigger" to={'/'}>Home</Link>
                                        </li>
                                       <li className="nav-item">
                                            <Link className="nav-link js-scroll-trigger" to={'/product'}>Product</Link>
                                        </li>
                                        <li className="nav-item">
                                            <Link className="nav-link js-scroll-trigger" to={'/add-product'}>AddProduct</Link>
                                        </li>
                                    </ul>
                                </div>
                            </div>
                        </nav>
                    </div>
                    <div style={{ margin: '100px' }}><RouterPath/></div>
                </div>
            </HashRouter >
        )
    }
}

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

Ở đây, mình đã tạo cho nó một thanh navbar chứa các điều hướng Router với các đường dẫn để tới được các Component. Và nội dung chính sẽ được render ra ở thẻ <div> có id là ‘example’ của file welcome.blade.php.

Trong folder component ta sẽ tạo ra thư mục pages sẽ chứa các file như sau:

  1. components/pages/Addproduct.js
  2. components/pages/Editproduct.js
  3. components/pages/Home.js
  4. components/pages/MyGlobleSetting.js
  5. components/pages/Product.js
  6. components/pages/MyGlobleSetting.js
  7. components/pages/RouterPath.js
  8. components/pages/TableRow.js

Bước 4:  Tạo file thêm sản phẩm và cấu hình Router

File: MyGlobleSetting.js

class MyGlobleSetting {
    constructor() {
      this.url = 'http://localhost:8888/crud_reactjs-laravel/public';
    }
  }
export default (new MyGlobleSetting);

Mình tạo file này để sau này gọi API cho tiện. Chỉ cần truy cập đến thuộc tính url theo kiểu hướng đối tượng là được, như vậy nhìn cũng có vẻ chuyên nghiệp hơn.

File: Appproduct.js

import React, {Component} from 'react';
import { browserHistory } from 'react-router';
import MyGlobleSetting from './MyGlobleSetting';
class AddProduct extends Component {
  constructor(props) {
    super(props);
    this.state = {
      productTitle: '',
      productBody: ''
    }
  }
  
  isChange = (event) => {
    const name = event.target.name;
    const value = event.target.value;
    this.setState({
      [name]:value
    });
  }

  handleSubmit = (event) => {
    event.preventDefault();
    const product = {
      title: this.state.productTitle,
      body: this.state.productBody  
    }
    let uri = MyGlobleSetting.url + '/api/products';
    axios.post(uri, product).then((response) => {
      window.location.href = MyGlobleSetting.url + '/#/product';
    });
  }
    render() {
      return (
      <div>
        <h1>Create A Product</h1>
        <form onSubmit={(event) => this.handleSubmit(event)}>
          <div className="row">
            <div className="col-md-6">
              <div className="form-group">
                <label>Product Title:</label>
                <input type="text" className="form-control" name="productTitle" onChange={(event) => {this.isChange(event)}} />
              </div>
            </div>
            </div>
            <div className="row">
              <div className="col-md-6">
                <div className="form-group">
                  <label>Product Body:</label>
                  <input type="text" className="form-control" name="productBody" onChange={(event) => {this.isChange(event)}}/>
                </div>
              </div>
            </div><br />
            <div className="form-group">
              <button className="btn btn-primary">Add Product</button>
            </div>
        </form>
  </div>
      )
    }
}
export default AddProduct;

Ở đây mình dùng arrow function trong ES6, nên lúc đầu bạn chạy sẽ bị lỗi. Bởi vì trong blade template của Laravel không hỗ trợ ES6 cho bạn. Để có thể sử dụng được arrow function bạn chạy lệnh sau trên terminal:

npm install --save-dev @babel/plugin-proposal-class-properties

Sau đó bạn phải tạo file .babelrc cùng cấp với thư mục root của project và thêm cấu hình sau:

{
    "plugins": [
      ["transform-class-properties", { "spec": true }]
    ]
}

Mình dùng axios để gọi API, nó là một dạng client HTTP của Javascript. Ngoài nó ra thì cũng có một số cách khác như dùng fetch(), Jquery,.. Tuy nhiên vì một số ưu điểm sau mà mình đã lựa chọn axios:

  • Xây dựng trên nền tảng Promise nên kế thừa được các ưu điểm của Promise
  • Cho phép thực hiện các hook khi ngay khi gửi request và nhận response
  • Cho phép hủy yêu cầu, đây cũng là một trong những điều đặc biệt mà các thư viện khác không có

File: RouterPath.js

import React, {Component} from 'react';
import {Route, Switch} from 'react-router-dom';
import Home from "./Home";
import editProduct from "./EditProduct";
import AddProduct from "./AddProduct";
import Product from "./Product";

class RouterPath extends Component {
    render() {
        return (
            <main>
                <Switch>
                    <Route exact path='/' component={Home}/>
                    <Route exact path='/add-product' component={AddProduct}/>
                    // Mình thêm sẵn 2 Router eidt-Product và Product luôn, tại lát nữa cũng sẽ dùng tới
                    <Route exact path='/edit-product/:id' component={editProduct}/> 
                    <Route exact path='/product' component={Product}/>
                </Switch>
            </main>
        )
    }
}

export default RouterPath;

Nếu mọi thứ ổn thì thằng Laravel Mix sẽ recompile và chạy lệnh php artisan serve, mở trình duyệt chạy URL bạn sẽ được kết quả như hình.

 

Thêm sản phẩm

Giao diện thêm sản phẩm

Bước 5: Hiển thị data ra ReactJS Frontend

Mấy thao tác này sẽ tương tự giống với phần AddProduct nên mình sẽ không đề cập nhiều về nó.

File: Home.js 

import React, {Component} from 'react';

class Home extends Component {
    render() {
        return (<div>
            <h1>Welcome to Home!</h1>
        </div>)
    }
}

export default Home;

File: Product.js 

import React, {Component} from 'react';
import axios from 'axios';
import { Link } from 'react-router';
import MyGlobleSetting from './MyGlobleSetting';
import TableRow from './TableRow';

class Product extends Component {
    constructor(props) {
        super(props);
        this.state = {value: '', products: ''};
    }
    componentDidMount() {
        axios.get(MyGlobleSetting.url + '/api/products')
            .then(response => {
                this.setState({ products: response.data });
            })
            .catch(function (error) {
                console.log(error);
            })
    }

    tabRow() {
        if(this.state.products instanceof Array){
            return this.state.products.map(function(object, i){
                return <TableRow obj={object} key={i} />;
            })
        }
    }

    render() {
        return (
            <div>
                <h1>Products List - Demo</h1>

                <div className="row">
                    <div className="col-md-10"></div>
                    
                    <div className="col-md-2">
                       <Link to="/add-product">Add Product</Link>
                    </div>
                    </div><br />
                <table className="table table-hover">
                    <thead>
                    <tr>
                        <td>ID</td>
                        <td>Product Title</td>
                        <td>Product Body</td>
                        <td width="200px">Actions</td>
                    </tr>
                    </thead>
                    <tbody>
                        { this.tabRow() }
                    </tbody>
                </table>
            </div>
        )
    }
}

export default Product;

File: TableRow.js 

import React, { Component } from 'react';
import { Link } from 'react-router';
 
class TableRow extends Component {
  render() {
    return (
        <tr>
          <td>
            {this.props.obj.id}
          </td>
          <td>
            {this.props.obj.title}
          </td>
          <td>
            {this.props.obj.body}
          </td>
          <td>
            
          <Link to={"edit-product/" + this.props.obj.id} className="btn btn-success" style={{marginRight: '5px'}}>Edit</Link>
          <Link to={"delete-product/" + this.props.obj.id} className="btn btn-danger">Delete</Link>
          </td>
        </tr>
    );
  }
}
 
export default TableRow;

Bước 6: Sửa và xóa Product

File: EditProduct.js 

import React, { Component } from 'react';
import MyGlobleSetting from './MyGlobleSetting';
class EditProduct extends Component {
  constructor(props) {
    super(props);
    this.state = {
      productTitle: '',
      productBody: ''
    }
  }
  
  componentDidMount(){
    // Router laravel
    axios.get(MyGlobleSetting.url + '/api/products/' + this.props.match.params.id + '/edit')
    .then(response => {
      this.setState({ productTitle: response.data.title, productBody: response.data.body });
    })
    .catch(function (error) {
      console.log(error);
    })
  }

  isChange = (event) => {
    const name = event.target.name;
    const value = event.target.value;
    this.setState({
      [name]:value
    });
  }

  handleSubmit = (event) => {
    event.preventDefault();
    const product = {
      title: this.state.productTitle,
      body: this.state.productBody  
    }
    let uri = MyGlobleSetting.url + '/api/products/' + this.props.match.params.id;
    axios.patch(uri, product).then((response) => {
      this.props.history.push('/product');
    });
  }
    render() {
      return (
      <div>
        <h1>Update Product</h1>
        <div className="row">
          <div className="col-md-9"></div>
        </div>
        <form onSubmit={(event) => this.handleSubmit(event)}>
          <div className="row">
            <div className="col-md-6">
              <div className="form-group">
                <label>Product Title:</label>
                <input type="text" className="form-control" name="productTitle" defaultValue={this.state.productTitle} onChange={(event) => {this.isChange(event)}}/>
              </div>
            </div>
            </div>
            <div className="row">
              <div className="col-md-6">
                <div className="form-group">
                  <label>Product Body:</label>
                  <input type="text" className="form-control" name="productBody" value={this.state.productBody} onChange={(event) => {this.isChange(event)}}/>
                </div>
              </div>
            </div><br />
            <div className="form-group">
              <button className="btn btn-primary">Update Product</button>
            </div>
        </form>
  </div>
      )
    }
}
export default EditProduct;

TÀI LIỆU THAM KHẢO

https://topdev.vn/blog/laravel-5-5-va-reactjs-xay-dung-crud-create-read-update-delete-tu-dau/

https://www.itsolutionstuff.com/post/laravel-5-simple-crud-application-using-reactjs-part-1example.html

https://codesource.io/build-a-crud-application-using-laravel-and-react/

Kết luận

Như vậy mình đã hướng dẫn các bạn xây dựng thành công ứng dụng CRUD đơn giản với Laravel 7+ và Reactjs. Tuy bài viết hơi dài, nhưng cũng không phải là phức tạp lắm. Dù vậy trong quá trình làm, bạn sẽ gặp không ít những khó khăn. Nếu gặp vấn đề vướng mắc, bạn đừng ngần ngại mà hãy comment vào phía dưới bài viết này, mình sẽ cùng bạn giải đáp những thắc mắc nếu như có thể. Bên cạnh đó, nếu như có những cách làm nào hay và tối ưu hơn thì bạn hãy đề xuất để mình có thể cải tiến ứng dụng một cách tốt hơn. Cảm ơn các bạn đã theo dõi bài viết, chúc các bạn và gia đình mạnh khỏe trong mùa dịch bệnh COVID-19 này nhé!

 

Bài viết liên quan

4 bình luận

Reactjs & Laravel 7 Xây dựng ứng dụng CRUD - Phần 1 | Blog Lê Thạch 12 Tháng Tư, 2020 - 11:08 chiều

[…] tiện theo dõi, thì mình sẽ tách Phần 2 ra ở một bài viết tiếp theo. Ở phần sau mình sẽ chủ yếu nói về các xử lý […]

Trả lời
huyi 1 Tháng Tám, 2020 - 11:34 sáng

tạo file .babelrc cùng cấp với thư mục root của project: là thư mực gì vậy ad?
import { HashRouter, Link } from ‘react-router-dom’: bị lỗi không tìm thấy cài này ?

Trả lời
Saheb 16 Tháng Bảy, 2021 - 7:58 chiều

Learn how to upload image with validation in laravel 8

Trả lời
Lê Thạch 17 Tháng Bảy, 2021 - 7:55 chiều

Yes. Thank for your comment. I will research more about it.

Trả lời

Thêm bình luận