ReactJS: Cách gọi API web với Hook useEffect trong React

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Giới thiệu

Trong quá trình phát triển React, các giao diện lập trình ứng dụng web (API) là một phần không thể thiếu trong các thiết kế ứng dụng một trang (SPA). API là cách chính để các ứng dụng giao tiếp theo chương trình với máy chủ để cung cấp cho người dùng dữ liệu thời gian thực và lưu các thay đổi của người dùng. Trong các ứng dụng React, bạn sẽ sử dụng các API để tải các tùy chọn của người dùng, hiển thị thông tin người dùng, tìm nạp cấu hình hoặc thông tin bảo mật và lưu các thay đổi trạng thái ứng dụng.

Trong bài hướng dẫn này, bạn sẽ sử dụng các Hook useEffect và useState để tìm nạp và hiển thị thông tin trong một ứng dụng mẫu, sử dụng máy chủ JSON làm API cục bộ cho mục đích thử nghiệm. Bạn sẽ tải thông tin khi một component được gắn kết lần đầu tiên và lưu dữ liệu đầu vào của khách hàng bằng một API. Bạn cũng sẽ làm mới dữ liệu khi người dùng thực hiện thay đổi và tìm hiểu cách bỏ qua các yêu cầu API khi một component ngắt kết nối. Đến cuối hướng dẫn này, bạn sẽ có thể kết nối các ứng dụng React của mình với nhiều API khác nhau và bạn sẽ có thể gửi và nhận dữ liệu thời gian thực.

Bước 1 - Tạo một dự án trống

Trong bước này, bạn sẽ tạo một dự án mới bằng Create React App. Sau đó, bạn sẽ xóa dự án mẫu và các tệp liên quan được cài đặt khi bạn khởi động dự án. Cuối cùng, bạn sẽ tạo một cấu trúc tệp đơn giản để tổ chức các component của mình. Điều này sẽ cung cấp cho bạn một cơ sở vững chắc để xây dựng ứng dụng mẫu của hướng dẫn này để tạo style trong bước tiếp theo.

Để bắt đầu, hãy thực hiện một dự án mới. Bạn mở terminal và chạy tập lệnh sau để cài đặt một dự án mới bằng cách sử dụng create-react-app:

npx create-react-app api-tutorial

Sau khi dự án kết thúc, hãy thay đổi vào thư mục:

cd api-tutorial

Khởi động dự án với lệnh:

npm start
Bạn sẽ nhận được một máy chủ cục bộ đang chạy. Nếu dự án không mở trong cửa sổ trình duyệt, bạn có thể mở nó bằng http://localhost:3000/. Nếu bạn đang chạy điều này từ một máy chủ từ xa, địa chỉ sẽ là .http://your_domain:3000

Trình duyệt của bạn sẽ tải với một ứng dụng React đơn giản được bao gồm như một phần của Create React App:

Dự án mẫu React

Bạn sẽ xây dựng một tập hợp các component tùy chỉnh hoàn toàn mới, vì vậy bạn sẽ cần bắt đầu bằng cách xóa một số mã soạn sẵn để bạn có thể có một dự án trống.

Để bắt đầu, hãy mở component src/App.js. Đây là component gốc được đưa vào trang. Tất cả các component sẽ bắt đầu từ đây. Bạn sửa lại file để trong đó chỉ còn chứa như sau:

import './App.css';

function App() {
  return <></>;
}

export default App;

Mở một terminal khác và thực hiện thao tác xóa file logo.svg:

rm src/logo.svg

Tạo thư mục components:

mkdir src/components

Tạo thư mục App:

mkdir src/components/App

Di chuyển các file App.* vào thư mục App:

mv src/App.* src/components/App

Mở file index.js và chỉnh sửa:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Lưu file lại và quay lại trang web ta được một trang web trống.

màn hình trống trong chrome

Bây giờ bạn đã hoàn thành dự án Tạo ứng dụng React mẫu, hãy tạo một cấu trúc tệp đơn giản. Điều này sẽ giúp bạn giữ cho các component của bạn cô lập và độc lập.

Tạo một thư mục được gọi components trong thư mục src, components sẽ là nơi chứa tất cả các component tùy chỉnh của bạn.

Bước 2 - Tạo dự án và API cục bộ

Trong bước này, bạn sẽ tạo API REST cục bộ bằng máy chủ JSON, máy chủ này sẽ sử dụng làm nguồn dữ liệu thử nghiệm. Sau đó, bạn sẽ xây dựng một ứng dụng để hiển thị danh sách hàng tạp hóa và thêm các mặt hàng vào danh sách. Máy chủ JSON sẽ là API cục bộ của bạn và sẽ cung cấp cho bạn một URL trực tiếp để thực hiện các yêu cầu GET và yêu cầu POST. Với API cục bộ, bạn có cơ hội tạo mẫu và thử nghiệm các component trong khi bạn hoặc một nhóm khác phát triển các API trực tiếp.

Đến cuối bước này, bạn sẽ có thể tạo các API giả cục bộ mà bạn có thể kết nối với các ứng dụng React của mình.

Trên nhiều nhóm agile, các nhóm front-end và API làm việc trên một vấn đề theo hình thức song song. Để phát triển ứng dụng giao diện người dùng trong khi API từ xa vẫn đang được phát triển, bạn có thể tạo phiên bản cục bộ mà bạn có thể sử dụng trong khi đợi API từ xa hoàn chỉnh.

Có nhiều cách để tạo một API cục bộ giả. Bạn có thể tạo một máy chủ đơn giản bằng Node hoặc một ngôn ngữ khác, nhưng cách nhanh nhất là sử dụng gói JSON server Node. Dự án này tạo một API REST cục bộ từ một file JSON.

Để bắt đầu, hãy cài đặt json-server:

npm install --save-dev json-server

Sau khi kết thúc cài đặt ta được thông báo dạng như sau:

158 packages are looking for funding
  run `npm fund` for details

10 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

json-server tạo một API dựa trên đối tượng JavaScript. Các key là các đường dẫn URL và các giá trị được trả về dưới dạng các phản hồi (response). You lưu trữ đối tượng JavaScript cục bộ và commit nó tới nguồn của bạn.

Mở file có tên db.json trong thư mục gốc của ứng dụng ra. File này chứa thông tin về các request (yêu cầu) của bạn từ API:

Thêm một đối tượng với key là list và một mảng các giá trị với một id và một key là item. Trong đây là danh sách các grocery. Key list sẽ cung cấp cho bạn một URL với một endpoint là /list:

{
  "list": [
    { "id": 1, "item": "bread" },
    { "id": 2, "item": "grapes" }
  ]
}

In đoạn mã trên, bạn có các dữ liệu cứng là bread và grapes như là điểm bắt đầu cho danh sách grocery.

Lưu file lại. Để chạy server API bạn sẽ sử dụng json-server từ dòng lệnh với một đối số trỏ tới file cấu hình API. Thêm nó như là một script trong package.json.

Mở file package.json ra, sau đó thêm một script để chạy API. Ngoài ra ta cần thêm một thuộc tính delay. Điều này sẽ tạo ra phản hồi, tạo một độ trễ giữa request API và response API. Điều này sẽ cho bạn thấy được một số điểm về cách ứng dụng sẽ làm việc thế nào trong khi chờ server phản hồi. Thêm độ trễ là 1500 milli giây. Cuối cùng ta chạy API ở cổng 3333 sử dụng tùy chọn -p để đảm bảo không xung đột với script chạy create-react-app:

{
  "name": "do-14-api",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.3"
  },
  "scripts": {
    "api": "json-server db.json -p 3333 --delay 1500",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "json-server": "^0.16.1"
  }
}

Lưu file lại. Mở một terminal mới ra và bạn hãy chạy server API với lệnh sau:

npm run api

Hãy để nó chạy trong suốt hướng dẫn này.

Khi bạn chạy câu lệnh thì bạn sẽ nhận được một output sẽ liệt kê các tài nguyên API:

> api-tutorial@0.1.0 api
> json-server db.json -p 3333 --delay 1500


  \{^_^}/ hi!

  Loading db.json
  Done

  Resources
  http://localhost:3333/list

  Home
  http://localhost:3333

  Type s + enter at any time to create a snapshot of the database
GET /%PUBLIC_URL%/favicon.ico 404 1521.342 ms - 2
GET /%PUBLIC_URL%/manifest.json 404 1522.246 ms - 2
GET /list 200 1526.113 ms - 87
GET /list 304 1549.301 ms - -
GET /list 304 1536.997 ms - -
GET /list 304 1536.176 ms - -

Mở http://localhost:3333/list và bạn sẽ thấy API như sau:

Khi bạn mở một điểm trong trình duyệt của mình, bạn sử dụng phương thức GET. Nhưng json-server không giới hạn ở phương thức GET. Bạn cũng có thể thực hiện nhiều phương thức REST khác. Ví dụ, bạn có thể POST các mặt hàng mới. Mở một cửa sổ terminal mới, hãy dùng curl để POST một mục mới với kiểu là application/json:

curl -d '{"item":"rice"}' -H 'Content-Type: application/json' -X POST http://localhost:3333/list

Lưu ý rằng bạn phải xâu chuỗi nội dung trước khi gửi. Sau khi chạy lệnh curl, bạn sẽ nhận được thông báo thành công:

Output

{ "item": "rice", "id": 3 }

Nếu bạn làm mới trình duyệt, mục mới sẽ xuất hiện:

Nội dung cập nhật, 2

Request POST cũng sẽ cập nhật file db.json. Hãy lưu ý đến những thay đổi, vì không có rào cản nào đối với việc vô tình lưu nội dung không có cấu trúc hoặc không hữu ích khi bạn làm việc trên ứng dụng của mình. Đảm bảo kiểm tra bất kỳ thay đổi nào trước khi chuyển sang kiểm soát phiên bản.

Trong bước này, bạn đã tạo một API cục bộ. Bạn đã học cách tạo tệp tĩnh với các giá trị mặc định và cách tìm nạp hoặc cập nhật các giá trị đó bằng cách sử dụng các hành động RESTful như GETvà POST. Trong bước tiếp theo, bạn sẽ tạo các dịch vụ để tìm nạp dữ liệu từ API và hiển thị trong ứng dụng của mình.

Bước 3 - Tìm nạp dữ liệu từ một API với useEffect

Trong bước này, bạn sẽ tìm nạp danh sách hàng tạp hóa bằng Hook useEffect. Bạn sẽ tạo một dịch vụ để sử dụng các API trong các thư mục riêng biệt và gọi dịch vụ đó trong các component. Sau khi bạn gọi dịch vụ, bạn sẽ lưu dữ liệu bằng Hook useState và hiển thị kết quả trong component của bạn.

Đến cuối bước này, bạn sẽ có thể gọi các API web bằng phương thức Fetch và Hook useEffect. Bạn cũng sẽ có thể lưu và hiển thị kết quả.

Bây giờ bạn đã có một API đang hoạt động, bạn cần một dịch vụ để tìm nạp dữ liệu và các component để hiển thị thông tin. Bắt đầu bằng cách tạo một dịch vụ. Bạn có thể tìm nạp dữ liệu trực tiếp bên trong bất kỳ component nào, nhưng các dự án của bạn sẽ dễ dàng duyệt và cập nhật hơn nếu bạn giữ các hàm truy xuất dữ liệu tách biệt với các component hiển thị của mình. Điều này sẽ cho phép bạn sử dụng lại các phương thức trên các component, mô phỏng trong các test và cập nhật URL khi các enpoint thay đổi.

Tạo một thư mục có tên services bên trong thư mục src:

mkdir src/services

Sau đó, tạo và mở file có tên list.js ra, bạn sẽ sử dụng file này cho bất kỳ hành động nào trên enpoint /list. Thêm một hàm để truy xuất dữ liệu bằng cách sử dụng hàm fetch:

export function getList() {
  return fetch('http://localhost:3333/list')
    .then(data => data.json())
}

Mục tiêu duy nhất của hàm này là truy cập dữ liệu và chuyển đổi phản hồi thành JSON bằng phương thức data.json()GET là hành động mặc định, vì vậy bạn không cần bất kỳ tham số nào khác.

Ngoài fetch ra, còn có các thư viện phổ biến khác như Axios có thể cung cấp cho bạn một giao diện trực quan và sẽ cho phép bạn thêm các tiêu đề mặc định hoặc thực hiện các hành động khác trên dịch vụ. Nhưng fetch sẽ hoạt động cho hầu hết các yêu cầu.

Lưu file lại. Tiếp theo, mở App.css và thêm một số style:

.wrapper {
    padding: 15px;
}

Lưu file lại. Bây giờ bạn cần thêm code để truy xuất dữ liệu và hiển thị nó trong ứng dụng của mình.

Mở App.js ra, trong các component function, bạn sử dụng Hook useEffect để tìm nạp dữ liệu khi component đó tải hoặc một số thông tin thay đổi. Để biết thêm thông tin về Hook useEffect, hãy xem bài viết Cách xử lý khi tải dữ liệu không đồng bộ, tải chậm và phân tách mã bằng React. Bạn cũng sẽ cần lưu kết quả bằng Hook useState.

Import useEffect và useState sau đó tạo một biến được gọi là list và một bộ định tuyến được gọi setList để giữ dữ liệu bạn tìm nạp từ dịch vụ bằng cách sử dụng Hook useState:

import { useEffect, useState } from 'react';
import './App.css';

function App() {
  const [list, setList] = useState([]);
  return <></>
}

export default App;

Tiếp theo, import dịch vụ, sau đó gọi dịch vụ bên trong Hook useEffect của bạn. Cập nhật list sử dụng setList nếu component được gắn kết. Để hiểu lý do tại sao bạn nên kiểm tra xem component đã được gắn kết hay chưa trước khi thiết lập dữ liệu, hãy xem Bước 3 - Ngăn ngừa lỗi trên các component chưa được gắn kết trong bài Cách xử lý tính năng tải dữ liệu không đồng bộ, tải chậm và phân tách mã bằng React.

Hiện tại, bạn chỉ chạy hiệu ứng một lần khi tải trang, vì vậy mảng phụ thuộc sẽ trống. Trong bước tiếp theo, bạn sẽ kích hoạt hiệu ứng dựa trên các hành động trên trang khác nhau để đảm bảo rằng bạn luôn có thông tin cập nhật nhất.

Thêm mã được đánh dấu sau vào App.js:

import { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';

function App() {
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  return <></>
}

export default App;

Cuối cùng, lặp lại các mục với .map và hiển thị chúng trong danh sách:

import { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';

function App() {
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  return(
    <div className="wrapper">
     <h1>My Grocery List</h1>
     <ul>
       {list.map(item => <li key={item.item}>{item.item}</li>)}
     </ul>
   </div>
  )
}

export default App;

Lưu file lại và quay lại trang web, bạn sẽ tìm thấy danh sách các mục:

Liệt kê các mục, 3

Như vậy trong bước này, bạn đã thiết lập một dịch vụ để lấy dữ liệu từ một API. Bạn đã học cách gọi dịch vụ bằng Hook useEffect và cách đặt dữ liệu trên trang. Bạn cũng đã hiển thị dữ liệu bên trong JSX của mình.

Trong bước tiếp theo, bạn sẽ gửi dữ liệu tới API bằng cách sử dụng POST và sử dụng phản hồi để thông báo cho người dùng của bạn rằng một hành động đã thành công.

Bước 4 - Gửi dữ liệu tới API

Trong bước này, bạn sẽ gửi dữ liệu trở lại một API bằng cách sử dụng API Fetch và phương thức gửi POST. Bạn sẽ tạo một component sẽ sử dụng biểu mẫu web để gửi dữ liệu với trình xử lý sự kiện onSubmit và sẽ hiển thị thông báo thành công khi hành động hoàn tất.

Khi kết thúc bước này, bạn sẽ có thể gửi thông tin đến một API và bạn sẽ có thể thông báo cho người dùng khi yêu cầu được giải quyết.

Gửi dữ liệu đến một dịch vụ

Bạn có một ứng dụng sẽ hiển thị danh sách các mặt hàng tạp hóa, nhưng nó không phải là một ứng dụng tạp hóa rất hữu ích trừ khi bạn cũng có thể lưu nội dung. Bạn cần tạo một dịch vụ để có thể POST một mục mới cho API.

Mở ra src/services/list.js ra rồi tạo thêm một hàm có một tham số là item và sẽ gửi dữ liệu bằng phương thức POST tới API. Ở đây bạn vẫn sử dụng API Fetch. Lần này, bạn sẽ cần thêm thông tin. Thêm một đối tượng tùy chọn làm đối số thứ hai. Bao gồm các phương thức gửi POST-cùng với header để thiết lập Content-Type là application/json. Cuối cùng, gửi đối tượng mới trong body. Bạn cũng cần đảm bảo chuyển đổi đối tượng thành một chuỗi bằng cách sử dụng JSON.stringify.

Khi bạn nhận được phản hồi, hãy chuyển đổi giá trị thành JSON:

export function getList() {
  return fetch('http://localhost:3333/list')
    .then(data => data.json())
}

export function setItem(item) {
 return fetch('http://localhost:3333/list', {
   method: 'POST',
   headers: {
     'Content-Type': 'application/json'
   },
   body: JSON.stringify({ item })
 })
   .then(data => data.json())
}

Lưu file lại.

Lưu ý: Trong các ứng dụng production, bạn sẽ cần thêm xử lý và kiểm tra lỗi. Ví dụ: nếu bạn viết sai chính tả cho endpoint, bạn vẫn nhận được phản hồi 404 và phương thức data.json() sẽ trả về một đối tượng trống. Để giải quyết vấn đề, thay vì chuyển đổi phản hồi thành JSON, bạn có thể kiểm tra thuộc tính data.ok. Nếu nó là false, bạn có thể tạo ra một lỗi và sau đó sử dụng phương thức .catch trong component của bạn để hiển thị thông báo lỗi cho người dùng.

Bây giờ bạn đã tạo xong dịch vụ, bạn cần sử dụng dịch vụ bên trong component của mình.

Mở App.js ra và thêm một phần tử form xung quanh một input và một button submit:

import { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';

function App() {
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  return(
    <div className="wrapper">
      <h1>My Grocery List</h1>
      <ul>
        {list.map(item => <li key={item.item}>{item.item}</li>)}
      </ul>
      <form>
       <label>
         <p>New Item</p>
         <input type="text" />
       </label>
       <button type="submit">Submit</button>
     </form>
    </div>
  )
}

export default App;

Hãy chắc chắn bao ngoài input với một label để form có thể truy cập bởi một trình đọc màn hình. Nó cũng là một thực tiễn tốt để thêm một type="submit" vào button để tường minh nhiệm vụ là gửi form.

Lưu file lại và quay lại trang web, bạn sẽ thấy form của mình.

Biểu mẫu danh sách tạp hóa

Tiếp theo, chuyển đổi input thành một thành phần được kiểm soát. Bạn sẽ cần một component được kiểm soát để có thể xóa dữ liệu của form sau khi người dùng gửi thành công một mục danh sách mới.

Đầu tiên, hãy tạo một trình xử lý state mới để giữ và đặt thông tin đầu vào bằng Hook useState:

import { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';

function App() {
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  return(
    <div className="wrapper">
      <h1>My Grocery List</h1>
      <ul>
        {list.map(item => <li key={item.item}>{item.item}</li>)}
      </ul>
      <form>
        <label>
          <p>New Item</p>
          <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Trong đoạn code trên, sau khi tạo trình xử lý state, hãy đặt giá trị của input thành itemInput và cập nhật giá trị bằng cách chuyển event.target.value tới hàm setItemInput bằng trình xử lý sự kiện onChange.

Giờ đây, người dùng của bạn có thể điền vào biểu mẫu với các mục danh sách mới. Tiếp theo, bạn sẽ kết nối biểu mẫu với dịch vụ của bạn.

Tạo một hàm được gọi là handleSubmithandleSubmit sẽ lấy một sự kiện làm đối số và sẽ gọi event.preventDefault() để ngăn biểu mẫu làm mới trình duyệt.

Import setItem từ dịch vụ, sau đó gọi setItem với giá trị itemInput bên trong hàm handleSubmit. Kết nối handleSubmit với form bằng cách truyền nó đến trình xử lý sự kiện onSubmit:

import { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  const handleSubmit = (e) => {
    e.preventDefault();
    setItem(itemInput)
  };

  return(
    <div className="wrapper">
      <h1>My Grocery List</h1>
      <ul>
        {list.map(item => <li key={item.item}>{item.item}</li>)}
      </ul>
      <form onSubmit={handleSubmit}>
        <label>
          <p>New Item</p>
          <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Lưu file lại và quay lại trang web, bạn điền nội dung vào input và nhấn nút Submit và bạn sẽ có thể gửi các giá trị. Lưu ý rằng bạn sẽ nhận được phản hồi thành công trong tab Network. Nhưng danh sách không cập nhật ngay trên trang web.

Gửi thành công, 5

Hiển thị một thông báo submit thành công

Luôn luôn là một phương pháp hay để cung cấp cho người dùng một số dấu hiệu cho thấy hành động của họ đã thành công. Nếu không, người dùng có thể thử và gửi lại một giá trị nhiều lần hoặc có thể nghĩ rằng hành động của họ không thành công và sẽ rời khỏi ứng dụng.

Để thực hiện việc này, hãy tạo một biến state và hàm setter với useState để cho biết có hiển thị cho người dùng một thông báo cảnh báo hay không. Nếu alert là true, hãy hiển thị một thẻ <h2> với thông báo Submit Successful.

Khi promise setItem được giải quyết, hãy xóa đầu vào và đặt thông báo cảnh báo:

import { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [alert, setAlert] = useState(false);
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  const handleSubmit = (e) => {
    e.preventDefault();
    setItem(itemInput)
      .then(() => {
        setItemInput('');
        setAlert(true);
      })
  };

  return(
    <div className="wrapper">
      <h1>My Grocery List</h1>
      <ul>
        {list.map(item => <li key={item.item}>{item.item}</li>)}
      </ul>
      {alert && <h2> Submit Successful</h2>}
      <form onSubmit={handleSubmit}>
        <label>
          <p>New Item</p>
          <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  )
}

export default App;

Lưu file lại và quay lại trang web, bạn sẽ thấy thông báo thành công sau khi yêu cầu API được giải quyết.

Gửi và nhắn tin, 6

Có nhiều cách tối ưu hóa khác mà bạn có thể thêm vào. Ví dụ: bạn có thể muốn disabled input của form trong khi có một yêu cầu đang hoạt động. Bạn có thể tìm hiểu thêm về cách disabled các phần tử biểu mẫu trong Cách tạo biểu mẫu trong React.

Bây giờ bạn đã thông báo người dùng rằng kết quả thành công, nhưng thông báo cảnh báo không biến mất và danh sách không cập nhật. Để khắc phục điều này, hãy bắt đầu bằng cách ẩn thông báo. Trong trường hợp này, bạn muốn ẩn thông tin sau một khoảng thời gian ngắn, chẳng hạn như một giây. Bạn có thể sử dụng hàm setTimeout để gọi setAlert(false), nhưng bạn sẽ cần phải đặt nó trong trong useEffect để đảm bảo rằng nó không chạy trên mọi component hiển thị.

Bên trong App.js bạn tạo hiệu ứng mới và truyền alert cho mảng trình kích hoạt. Điều này sẽ làm cho hiệu ứng chạy mỗi khi alert thay đổi. Lưu ý rằng điều này sẽ chạy nếu alert thay đổi từ falsesang true, nhưng nó cũng sẽ chạy khi alert thay đổi từ true sang false. Vì bạn chỉ muốn ẩn cảnh báo nếu nó được hiển thị, hãy thêm điều kiện bên trong hiệu ứng để chỉ chạy setTimeout nếu alert là true:

import { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [alert, setAlert] = useState(false);
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);
  useEffect(() => {
    let mounted = true;
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [])

  useEffect(() => {
    if(alert) {
      setTimeout(() => {
        setAlert(false);
      }, 1000)
    }
  }, [alert])

  const handleSubmit = (e) => {
    e.preventDefault();
    setItem(itemInput)
      .then(() => {
        setItemInput('');
        setAlert(true);
      })
  };

  return(
    <div className="wrapper">
      ...
    </div>
  )
}

export default App;

Chạy hàm setTimeout sau 1000 mili giây để đảm bảo người dùng có thời gian đọc thay đổi.

Lưu file lại và quay lại trang web. Bây giờ bạn có một hiệu ứng sẽ chạy bất cứ khi nào alert thay đổi. Nếu có một thông báo hoạt động, nó sẽ bắt đầu hàm setTimeout mà sẽ đóng cảnh báo sau một giây.

Ẩn cảnh báo, 7

Làm mới dữ liệu đã fetch (tìm nạp)

Bây giờ bạn cần một cách để làm mới danh sách dữ liệu cũ. Để làm điều này, bạn có thể thêm một trình kích hoạt mới vào Hook useEffect để chạy lại request getList. Để đảm bảo bạn có dữ liệu phù hợp nhất, bạn cần một trình kích hoạt sẽ cập nhật bất kỳ lúc nào có thay đổi đối với dữ liệu từ xa. May mắn thay, bạn có thể sử dụng lại state alert để kích hoạt một lần làm mới dữ liệu khác vì bạn biết rằng nó sẽ chạy bất cứ khi nào người dùng cập nhật dữ liệu. Như vậy là bạn phải lập kế hoạch cho thực tế là hiệu ứng sẽ chạy mỗi khi alert thay đổi kể cả khi thông báo cảnh báo biến mất.

Lần này, hiệu ứng cũng cần tìm nạp dữ liệu khi tải trang. Ta sẽ tạo một điều kiện để sẽ thoát khỏi hàm trước khi dữ liệu lấy nếu list.length là true - tức là bạn đã lấy các dữ liệu - và alert là false - tức là bạn đã làm mới dữ liệu. Đảm bảo thêm alert và list vào mảng trình kích hoạt:

import { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [alert, setAlert] = useState(false);
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);

  useEffect(() => {
    let mounted = true;
    if(list.length && !alert) {
      return;
    }
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [alert, list])

  ...

  return(
    <div className="wrapper">
      ...
    </div>
  )
}

export default App;

Lưu file lại và quay lại trang web, dữ liệu sẽ làm mới sau khi bạn gửi một mục mới:

Làm mới danh sách, 8

Trong trường hợp này, alert không liên quan trực tiếp đến state list. Tuy nhiên, nó xảy ra cùng lúc với một sự kiện sẽ làm mất hiệu lực của dữ liệu cũ, vì vậy bạn có thể sử dụng nó để làm mới dữ liệu.

Ngăn cập nhật trên các component chưa được gắn kết

Vấn đề cuối cùng bạn cần tính đến là đảm bảo rằng bạn không đặt state trên một component chưa được gắn kết. Bạn có một giải pháp cho vấn đề với lệnh let mounted = true trong hiệu ứng của mình để tìm nạp dữ liệu, nhưng giải pháp sẽ không hoạt động đối với handleSubmit vì nó không phải là một hiệu ứng. Bạn không thể trả về một hàm để đặt giá trị thành false khi nó được ngắt kết nối. Hơn nữa, sẽ không hiệu quả nếu thêm cùng một kiểm tra cho mọi hàm.

Để giải quyết vấn đề này, bạn có thể tạo một biến chia sẻ được sử dụng bởi nhiều hàm bằng cách bỏ mounted ra khỏi useEffect và đặt nó ở vị trí cùng mức của component. Bạn sẽ vẫn sử dụng hàm để đặt giá trị false ở cuối useEffect.

Bên trong App.js, khai báo mounted trên đầu hàm. Sau đó, kiểm tra xem component đã được gắn kết chưa trước khi thiết lập dữ liệu trong các hàm không đồng bộ khác. Đảm bảo loại bỏ khai báo mounted bên trong useEffect:

import { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [alert, setAlert] = useState(false);
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);
  let mounted = true;

  useEffect(() => {
    if(list.length && !alert) {
      return;
    }
    getList()
      .then(items => {
        if(mounted) {
          setList(items)
        }
      })
    return () => mounted = false;
  }, [alert, list])

  useEffect(() => {
    if(alert) {
      setTimeout(() => {
        if(mounted) {
          setAlert(false);
        }
      }, 1000)
    }
  }, [alert])

  const handleSubmit = (e) => {
    e.preventDefault();
    setItem(itemInput)
      .then(() => {
        if(mounted) {
          setItemInput('');
          setAlert(true);
        }
      })
  };

  return(
    <div className="wrapper">
      ...
    </div>
  )
}

export default App;

Khi bạn thực hiện thay đổi, bạn sẽ nhận được lỗi dạng như sau trong teriminal nơi bạn đang chạy ứng dụng React của mình:

Error

Assignments to the 'mounted' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect react-hooks/exhaustive-deps

React đang cảnh báo bạn rằng các biến không ổn định. Bất cứ khi nào có kết xuất lại, nó sẽ tính toán lại biến. Thông thường, điều này sẽ đảm bảo thông tin cập nhật. Trong trường hợp này, bạn đang dựa vào biến đó để tồn tại.

Giải pháp là ta sử dụng một Hook khác được gọi là useRef. Hook useRef sẽ duy trì một biến cho tuổi thọ của các component. Bí quyết duy nhất là lấy giá trị bạn cần để sử dụng state .current.

Bên trong App.js, chuyển đổi mounted thành tham chiếu bằng Hook useRef. Sau đó, chuyển đổi từng cách sử dụng mounted thành mounted.current:

import { useEffect, useRef, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';

function App() {
  const [alert, setAlert] = useState(false);
  const [itemInput, setItemInput] = useState('');
  const [list, setList] = useState([]);
  const mounted = useRef(true);

  useEffect(() => {
    mounted.current = true;
    if(list.length && !alert) {
      return;
    }
    getList()
      .then(items => {
        if(mounted.current) {
          setList(items)
        }
      })
    return () => mounted.current = false;
  }, [alert, list])

  useEffect(() => {
    if(alert) {
      setTimeout(() => {
        if(mounted.current) {
          setAlert(false);
        }
      }, 1000)
    }
  }, [alert])

  const handleSubmit = (e) => {
    e.preventDefault();
    setItem(itemInput)
      .then(() => {
        if(mounted.current) {
          setItemInput('');
          setAlert(true);
        }
      })
  };

  return(
    <div className="wrapper">
       ...
    </div>
  )
}

export default App;

Ngoài ra, hãy thận trọng về việc đặt biến trong hàm cleanup cho useEffect. Hàm cleanup sẽ luôn chạy trước khi hiệu ứng chạy lại. Điều đó có nghĩa là hàm cleanup () => mounted.current = false sẽ chạy mỗi khi thay đổi alert hoặc list. Để tránh bất kỳ kết quả sai nào, hãy chắc chắn cập nhật mounted.current thành true lúc bắt đầu của hiệu ứng. Sau đó, bạn có thể chắc chắn rằng nó sẽ chỉ được đặt thành false khi component được ngắt kết nối.

Lưu file lại và quay lại trang web bạn refresh lại trang, thì lúc này bạn sẽ có thể thấy hiệu ứng rằng sau khi thông báo được tắt thì sẽ hiện ra mục mà bạn vừa thêm vào:

Đang lưu lại, 9

Lưu ý: Việc vô tình chạy lại một API nhiều lần là một vấn đề phổ biến. Mỗi khi một component bị xóa và sau đó được gắn lại, bạn sẽ chạy lại tất cả quá trình tìm nạp dữ liệu ban đầu. Để tránh điều này, hãy xem xét phương pháp lưu vào bộ nhớ đệm cho các API đặc biệt nặng hoặc chậm. Bạn có thể sử dụng bất cứ thứ gì từ ghi nhớ các lời gọi dịch vụ, đến bộ nhớ đệm với server worker, cho đến một Hook tùy chỉnh. Có một số Hook tùy chỉnh phổ biến cho các lời gọi dịch vụ bộ nhớ đệm, bao gồm useSWR và truy vấn phản ứng.

Cho dù bạn sử dụng cách tiếp cận nào, hãy chắc chắn xem xét cách bạn sẽ làm mất hiệu lực bộ nhớ cache vì đôi khi bạn sẽ muốn tìm nạp dữ liệu mới nhất.

Như vậy trong bước này, bạn đã gửi dữ liệu đến một API. Bạn đã học cách cập nhật người dùng khi dữ liệu được gửi và cách kích hoạt làm mới dữ liệu danh sách của bạn. Bạn cũng tránh thiết lập dữ liệu trên các component chưa được gắn kết bằng cách sử dụng Hook useRef để lưu trữ trạng thái của component để nó có thể được sử dụng bởi nhiều dịch vụ.

Phần kết luận

API cung cấp cho bạn khả năng kết nối với nhiều dịch vụ hữu ích. Chúng cho phép bạn lưu trữ và truy xuất dữ liệu ngay cả sau khi người dùng đóng trình duyệt của họ hoặc ngừng sử dụng ứng dụng. Với mã được tổ chức tốt, bạn có thể cô lập các dịch vụ của mình khỏi các component của mình để các component của bạn có thể tập trung vào việc hiển thị dữ liệu mà không cần biết dữ liệu bắt nguồn từ đâu. API Web mở rộng ứng dụng của bạn vượt xa khả năng của một phiên trình duyệt hoặc bộ nhớ. Họ mở ứng dụng của bạn cho toàn bộ thế giới công nghệ web.

» Tiếp: Cách quản lý state trong React với Redux
« Trước: Cách xử lý khi tải dữ liệu không đồng bộ (async), tải chậm (lazy loading) và phân tách mã bằng React
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!