Khi quản lý trạng thái phức tạp trong app Next, mọi thứ có thể trở nên khó khăn. Tuy nhiên, bạn có thể khắc phục bằng cách tìm hiểu nguyên tắc cơ bản của React và quản lý trạng thái qua ứng dụng kinh điển này.
Chuẩn bị trước khi bắt đầu
Trước khi bắt đầu làm app liệt kê việc cần làm, bạn phải chuẩn bị:
- Kiến thức cơ bản về các toán tử JavaScript hiện đại và hook useState của React.
- Hiểu cách hủy cấu trúc mảng và đối tượng trong JavaScript.
- Node 16.8 trở lên được cài đặt trên máy cục bộ và biết cách dùng trình quản lý package như npm hoặc yarn.
Hiểu trạng thái và quản lý ứng dụng
Trạng thái ứng dụng chỉ điều kiện của app hiện tại ở một thời điểm nhất định, bao gồm thông tin & quản lý app, như input người dùng và tìm nạp dữ liệu từ database hoặc một API (Application Programming Interface).
Để hiểu trạng thái ứng dụng, hãy cân nhắc các trường hợp có thể của một app bộ đếm đơn giản, bao gồm:
- Trạng thái mặc định khi bộ đếm ở mức 0.
- Trạng thái tăng lên khi bộ đếm tăng lên 1.
- Trạng thái giảm xuống khi bộ đếm giảm 1.
- Trạng thái reset khi bộ đếm trở về mức mặc định.
Một thành phần React có thể đăng ký thay đổi trạng thái. Khi người dùng tương tác với một thành phần như thế, những tác vụ như click nút bấm có thể update trạng thái này.
Đoạn code dưới hiện một ứng dụng bộ đếm đơn giản, ở trạng thái mặc định, được dùng để quản lý trạng thái các tác vụ click:
const [counter, setCounter] = useState(0);
return (
<main>
<h1>{counter}</h1>
<button onClick={() => setCounter(counter + 1)}>increase</button>
<button onClick={() => setCounter(counter - 1)}>decrease</button>
<button onClick={() => setCounter(0)}>reset</button>
</main>
);
Thiết lập và cài đặt
Repository của dự án chứa hai nhánh: starter và context. Bạn có thể dùng nhánh khởi đầu làm cơ sở để xây dựng dự án từ đầu hoặc nhánh ngữ cảnh để xem trước bản demo cuối cùng.
Nhân bản ứng dụng Starter
App khởi đầu cung cấp UI bạn cần cho ứng dụng cuối cùng, vì thế, bạn có thể tập trung vào triển khai logic cốt lõi. Mở terminal và chạy lệnh sau để nhân bản nhánh starter của repository vào máy cục bộ:
git clone -b starter https://github.com/makeuseofcode/Next.js-CRUD-todo-app.git
Chạy lệnh sau, trong thư mục dự án để cài đặt các phần phụ thuộc và khởi chạy server phát triển:
yarn && yarn dev
Hoặc:
npm i && npm run dev
Nếu tất cả đều chạy tốt, UI sẽ hiện trong trình duyệt của bạn:
Triển khai logic
Context API cung cấp một cách để quản lý và chia sẻ dữ liệu trạng thái trên các thành phần mà không cần prop drilling theo cách thủ công.
Bước 1: Tạo và xuất ngữ cảnh
Tạo thư mục src/app/context để lưu file ngữ cảnh và giữ thư mục dự án được tổ chức tốt. Trong thư mục này, tạo file todo.context.jsx chứa tất cả logic ngữ cảnh cho ứng dụng này.
Nhập hàm createContext từ thư viện react và gọi nó, lưu trữ kết quả trong một biến:
import { createContext} from "react";
const TodoContext = createContext();
Tiếp theo, tạo một hook useTodoContext trả về TodoContext dưới dạng biểu mẫu khả dụng.
export const useTodoContext = () => useContext(TodoContext);
Bước 2: Tạo và quản lý trạng thái
Để thực hiện CRUD (tạo, đọc, update, xóa) của ứng dụng, bạn cần tạo trạng thái và quản lý chúng với thành phần Provider.
const TodoContextProvider = ({ children }) => {
const [task, setTask] = useState("");
const [tasks, setTasks] = useState([]);
return <TodoContext.Provider value={{}}>{children}</TodoContext.Provider>;
};
export default TodoContextProvider;
Ngay trước lệnh return, tạo hàm handleTodoInput chạy khi các kiểu người dùng nằm trong to-do. Hàm này sau đó update trạng thái task.
const handleTodoInput = (input) => setTask(input);
Thêm hàm createTask chạy khi người dùng gửi một app to-do. Hàm này update trạng thái tasks và gắn ID ngẫu nhiên cho nhiệm vụ mới.
const createTask = (e) => {
e.preventDefault();
setTasks([
{
id: Math.trunc(Math.random() * 1000 + 1),
task,
},
...tasks,
]);
};
Tạo hàm updateTask ánh xạ qua danh sách tasks và update nhiệm vụ chứa ID khớp với ID của nhiệm vụ được click.
const updateTask = (id, updateText) =>
setTasks(tasks.map((t) => (t.id === id ? { ...t, task: updateText } : t)));
Tạo hàm deleteTask update danh sách tasks để nó bao gồm tất cả nhiệm vụ chứa ID không khớp với tham số được cung cấp.
const deleteTask = (id) => setTasks(tasks.filter((t) => t.id !== id));
Bước 3: Thêm trạng thái và trình xử lý cho nhà cung cấp
Giờ bạn đã tạo trạng thái và viết code để quản lý chúng, bạn cần làm những hàm trạng thái và xử lý có sẵn cho Provider. Bạn có thể cung cấp chúng dưới dạng một đối tượng, dùng thuộc tính value của thành phần Provider.
return (
<TodoContext.Provider
value={{
task,
tasks,
handleTodoInput,
createTask,
updateTask,
deleteTask,
}}
>
{children}
</TodoContext.Provider>
);
Bước 4: Phạm vi bối cảnh
Provider đã tạo phải chứa thành phần top-level để tạo ngữ cảnh sẵn có cho toàn bộ ứng dụng. Để làm việc này, chỉnh sửa src/app/page.jsx và bao gồm thành phần Todos với TodoContextProvider:
<TodoContextProvider>
<Todos />;
</TodoContextProvider>;
Bước 5: Dùng ngữ cảnh trong Components
Chỉnh sửa file src/app/components/Todos.jsx và hủy cấu trúc tasks, task, handleTodoInput, và createTask qua cuộc gọi tới hàm useTodoContext.
const { task, tasks, handleTodoInput, createTask } = useTodoContext();
Giờ, update thành phần biểu mẫu để xử lý sự kiện gửi và đổi sang trường đầu vào chính:
<form onSubmit={(e) => createTask(e)}>
<input className="todo-input" type="text" placeholder="Enter a task" required value={task} onChange={(e) => handleTodoInput(e.target.value)} />
<input className="submit-todo" type="submit" value="Add task" />
</form>
Bước 6: Hiện nhiệm vụ trong UI
Giờ bạn có thể dùng ứng dụng để tạo và thêm một nhiệm vụ vào list tasks. Để update hiển thị, bạn cần ánh xạ qua tasks hiện có và hiện chúng trong UI. Đầu tiên, tạo src/app/components/Todo.jsx chứa một mục cần làm.
Trong src/app/components/Todo.jsx, chỉnh sửa hoặc xóa một nhiệm vụ bằng cách gọi các hàm updateTask và deleteTask đã tạo trong tệp src/app/context/todo.context.jsx.
import React, { useState } from "react";
import { useTodoContext } from "../context/todo.context";
const Todo = ({ task }) => {
const { updateTask, deleteTask } = useTodoContext();
// isEdit state tracks when a task is in edit mode
const [isEdit, setIsEdit] = useState(false);
return (
<table className="todo-wrapper">
<tbody>
<tr>
{isEdit ? ( <input type="text" value={task.task}
onChange={(e) => updateTask(task.id, e.target.value)} /> ) :
(<th className="task">{task.task}</th> )}
<td className="actions">
<button className="edit"
onClick={() => setIsEdit(!isEdit)}> {isEdit ? "Save" : "Edit"} </button>
<button onClick={() => deleteTask(task.id)}>Del</button>
</td>
</tr>
</tbody>
</table>
);
};
export default Todo;
Để hiện src/app/components/Todo.jsx cho từng task, đi sâu vào file src/app/components/Todos.jsx và ánh xạ có điều kiện qua tasks ngay sau khi đóng thẻ header.
{tasks && (
<main>
{tasks.map((task, i) => ( <Todo key={i} task={task} /> ))}
</main>
)}
Kiểm tra ứng dụng trong trình duyệt và xác nhận nó cho kết quả mong đợi.
Hi vọng bài viết hữu ích với các bạn.