Trong bài này, chúng ta sẽ học cách tạo bảng table, với dữ liệu động sử dụng reactjs. Chúng ta sẽ xem các thao tác lấy dữ liệu qua web service api và tạo các table row, sau đó là chức năng xoá dữ liệu kèm theo xoá row. Tất cả đều xử dụng reactjs component.

Chức năng

Chúng ta sẽ xây dựng chức năng quản lý danh sách việc cần làm (Todo List) đơn giản, tạm gọi là todo. Mỗi todo sẽ có id, name (tên việc), status (trạng thái). Trạng thái có thể là chưa xong (pending) hoặc đã xong (done).

Người sử dụng có thể tạo todo mới bằng cách nhập tên việc và trạng thái.

Danh sách các đầu việc sẽ được hiện ở dạng bảng.

Chúng ta sẽ dùng 3 api sau để ghi nhận thay đổi dữ liệu với cơ sở dữ liệu MySQL. Bạn có thể download và chạy backend được viết bằng Laravel tại đây.

  1. GET /api/todo: Lấy danh sách việc
  2. POST /api/todo: Tạo việc mới
  3. DELETE /api/todo/{id}: Xoá todo theo id

Tạo reactjs component cho table <table> và row <tr>

Trong thư mục src/componentstạo file TodoTable.js với đoạn code sau:

import React, { useState } from 'react';

function TodoTableRow({ id, name, status }) {
  return (
    <tr>
      <td>{name}</td>
      <td>{status == 1 ? <span>Done</span> : <span>Pending</span>}</td>
      <td>
        <input type="hidden" value={id}/>
        <button>Delete</button>
      </td>
    </tr>
  );
}

function TodoTable({todoData}) {
  const [rows, setRows] = useState(todoData);

  function handleAddRow() {
    setRows([...rows, { name: "", status: "" }]);
  }

  function handleChange(e, index) {
    const newRows = [...rows];
    newRows[index][e.target.name] = e.target.value;
    setRows(newRows);
  }

  return (
    <div>
      <table className='table'>
        <thead>
          <tr>
            <th>Name</th>
            <th>Status</th>
            <th>Action</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <TodoTableRow
              id={row.id}
              name={row.name}
              status={row.status}
            />
          ))}
        </tbody>
      </table>
      <button onClick={handleAddRow}>Add Row</button>
    </div>
  );
}

export default TodoTable;

Component TodoTableRow là reacjts component được viết ở dạng function (“functional component”),

  1. TodoTableRow in ra HTML cho từng row của todo.
  2. TodoTableRow nhận vào ba props làm đầu vào: id, name, và status.
  3. Component này trả về một thẻ <tr>, trong đó có chứa ba thẻ <td>. Thẻ đầu tiên chứa giá trị của name, thẻ thứ hai chứa một <span> element với nội dung là “Done” nếu giá trị của status bằng 1, hoặc “Pending” nếu giá trị của status khác 1. Thẻ thứ ba chứa một input có kiểu là “hidden” với giá trị là id và một button với nội dung là “Delete”.

Reactjs component TodoTable:

  1. Nhận vào một prop todoData.
  2. Sử dụng useState để khởi tạo một state variable rows với giá trị là todoData. Chúng ta gán dữ liệu json của các todo vào todoData từ trang TodoList ở mục sau.
  3. Hàm handleAddRow để thêm một dòng với giá trị mặc định là { name: "", status: "" }.
  4. Còn hàm handleChange dùng để thay đổi giá trị của một dòng (không thuộc phần của bài học này).

 

Giao diện cho trang Todo

Tiếp theo chúng ta xây dựng giao diện cho trang Todo sử dụng reactjs cơmponent. Múc đích của trang là lấy dữ liệu từ server và gọi component TodoTable ở trên để in dữ liệu ra màn hình.

Trong thư mục src/screensthêm file TodoList.js với đoạn code dưới đây:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import TodoTable from '../components/TodoTable';
import 'bootstrap/dist/css/bootstrap.min.css';
import DefaultLayout from '../layouts/DefaultLayout';

function TodoList() {
  const [todoData, setTodoData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await axios.get('http://localhost:5051/api/todo');
      setTodoData(response.data);
    }
    fetchData();
  }, []);

  return (
    <DefaultLayout title="To-do List">
      {todoData.length ? <TodoTable todoData={todoData} /> : <div> Loading... </div> }
    </DefaultLayout>
  );
}

export default TodoList;

Đoạn code này định nghĩa reactjs component TodoList, cũng là màn hình chính của chức năng quản lý todo này.

  1. useState khởi tạo một biến todoData (trong reactjs gọi là state variable) với giá trị ban đầu là một mảng rỗng.
  2. useEffect dùng axios để API tại http://localhost:5051/api/todo và cập nhật todoData với dữ liệu json trả về từ API.
  3. Trong render method return, component này sử dụng DefaultLayout component chúng ta đã xây dựng ở bài trước để hiển thị nội dung với tiêu đề là “To-do List”. Nó cũng sử dụng TodoTable component để hiển thị bảng với dữ liệu là todoData , nếu dữ liệu todoData không tồn tại, ứng dụng sẽ hiển thị thông báo ‘Loading…’.

Bạn nhớ cài đặt Laravel api, chạy backend server. Sau đó bạn có thể chạy thử ứng dụng reactjs của mình và sẽ thấy kết quả giống như màn hình dưới đây:

Học lập trình reactjs

Giải thích thêm về reactjs useEffect

useEffect là một Hook trong React được sử dụng để giải quyết các tác vụ có ảnh hưởng tới các thành phần trong một component, chẳng hạn như gọi API, tương tác với DOM hoặc lưu trữ trạng thái. useEffect của reactjs cho phép bạn “đăng ký” một hàm để chạy sau khi component đã được render lần đầu và sau mỗi lần render tiếp theo.

Sử dụng useEffect trong TodoList component: khi component này được render lần đầu, nó sẽ gọi hàm fetchData để lấy dữ liệu từ API và cập nhật todoData state variable. Khi component được render lại với giá trị mới của todoData, nó sẽ giải quyết tác vụ cập nhật UI với dữ liệu mới.

Trong reactjs, useEffect có 2 tham số chính:

  1. Tham số đầu tiên là hàm side-effect (thường là một async function) mà bạn muốn chạy sau mỗi lần render. Hàm này có thể truy cập vào props và state của component.
  2. Tham số thứ hai là một mảng các giá trị state hoặc props mà bạn muốn theo dõi. Nếu bạn không chỉ định giá trị nào, hàm sẽ chạy sau mỗi lần render. Nếu bạn chỉ định một giá trị, hàm sẽ chạy sau khi giá trị đó thay đổi.

Đọc thêm về useEffect của reactjs tại đây.

Xây dựng chức năng thêm mới

Để có thêm chức năng thêm 1 Todo mới, chúng ta cần update form cuối bảng để nhận vào 2 input là name và status của todo. Sau đó submit form và gọi api POST /api/todo/để lưu dữ liệu vào cơ sở dữ liệu trên server. Sau khi lưu thành công, cập nhật bảng trên giao diện.

Cập nhật form cuối bảng như sau:

<table>
...
</table>
<div className="row g-3">
  <div className="col-auto">
    <input type="text" className='form-control' name="name" value={newTodo.name} onChange={handleChange} placeholder="Name" />
  </div>
  <div className="col-auto">
    <select name="status" className='form-control' value={newTodo.status} onChange={handleChange}>
      <option>Select</option>
      <option value={0}>Pending</option>
      <option value={1}>Done</option>
    </select>
  </div>
  <div className="col-auto">
    <button onClick={handleAddTodo} className='btn btn-primary mb-3'>Add</button>
  </div>
</div>
  • Input đầu tiên sử dụng thuộc tính “name” là "name" và giá trị mặc định là giá trị của newTodo.name, và sự kiện onChange để gọi hàm handleChange khi người dùng nhập vào.
    Input thứ hai (select) chứa một select dropdown để chọn trạng thái của todo mới. Có 2 option là “Pending” và “Done”. select này cũng sử dụng thuộc tính “name” là "status" và giá trị mặc định là giá trị của newTodo.status, và sự kiện onChange để gọi hàm handleChange khi người dùng chọn một option.
  • Sự kiện onChange được gọi khi người sử dụng thay đổi giá trị của input đó.
  • Nút “Add” thêm todo mới vào danh sách, sử dụng sự kiện onClick để gọi hàm handleAddTodo khi người dùng nhấn vào nút này.
  • Tham khảo thêm về css cho form của bootstrap 5 tại đây.

Cập nhật hàm handleChange:

function handleChange(e) {
  setNewTodo({
    ...newTodo,
    [e.target.name]: e.target.value
  });
}
  • ... (spread operator) được sử dụng trong dòng code này để tạo ra một đối tượng mới là bản sao của đối tượng newTodo hiện tại. Khi chúng ta sử dụng ...newTodo, nó sẽ lấy tất cả các thuộc tính của đối tượng newTodo và thêm chúng vào một đối tượng mới.
  • [e.target.name]: e.target.value là thuộc tính mới đang được thêm vào đối tượng mới, đó là giá trị được cập nhật từ trường input. Ở trường hợp này khi có thay đổi ở trường name chúng ta sẽ có "name": "giá trị user nhập". Nếu là trường status: "status": "giá trị user chọn"
  • Vì vậy, tổng quan, nó tạo ra một đối tượng mới với tất cả các thuộc tính của đối tượng newTodo và giá trị cập nhật cho thuộc tính cụ thể. Và nó sẽ cập nhật trạng thái bằng cách gọi setNewTodo với đối tượng mới này, và từ đó cập nhật giá trị của thuộc tính cụ thể đó

Thêm hàm handleAddTodo:

async function handleAddTodo() {
  try {
    const response = await axios.post('http://localhost:5051/api/todo', newTodo);
      setRows([...rows, response.data]);
      setNewTodo({ name: '', status: '' });
  } catch (err) {
    console.error(err);
  }
}

Đoạn code trên là hàm handleAddTodo được gọi khi người dùng nhấn nút “Add” để thêm todo mới vào danh sách. Hàm này sử dụng axios để gửi một yêu cầu POST đến api /api/todo với dữ liệu là todo mới được nhập từ form (newTodo).

  • Trong hàm này sử dụng try catch để bắt lỗi.
  • Trong khối try:
    • Gọi axios.post() với đường dẫn và dữ liệu được truyền vào. Lấy response từ server
    • Sau khi thêm thành công, sử dụng setRows để thêm vào state rows dữ liệu trả về từ server
    • Sử dụng setNewTodo để reset form sau khi thêm thành công.
  • Trong khối catch, nếu có lỗi xảy ra, hàm sẽ in ra lỗi đó trong console bằng cách sử dụng console.error(err)

Với hàm này, sau khi thêm thành công, todo mới sẽ được thêm vào state rows và hiển thị trên giao diện, form sẽ được reset lại để người dùng có thể thêm todo tiếp theo.

Github code

Bạn có thể download code của toàn bộ series lập trình reactjs tại đây.

Bài tiếp theo

ReactJS Bài 7: Dùng logging để ghi lại thông tin, giúp gỡ rối hiệu quả

Lời kết

Và cuối cùng, người ta nói thái độ là tất cả. Vì thế bạn đừng quên các kỹ năng mềm quan trọng sau nếu muốn có 1 sự nghiệp thành công: 5 Kỹ năng mềm cần thiết cho lập trình viên muốn thành team lead

Leave a Reply

Discover more from Bệ Phóng Việt

Subscribe now to keep reading and get access to the full archive.

Continue reading