useEffect, useRef & Local Storage
Tóm tắt
useState
Dùng khi cần render lại UI khi giá trị thay đổi.
useEffect
Dùng để xử lý side effects khi component mount, update, unmount.
Đặc biệt khi cần làm thêm việc gì đó bên ngoài React (nghĩa là những trigger thay đổi không đến từ bên trong Components mà đến từ những yếu tố bên ngoài).
useRef
Dùng để lưu biến cố định trong suốt vòng đời component, nhưng không trigger render khi thay đổi.
localStorage
Lưu dữ liệu persistent giữa các phiên làm việc.
Component Lifecycle
3 trạng thái quan trọng của DOM:
Traditionally, React components were written with classes and there were certain lifecylce methods that we could use to do something when a component was created, updated, or removed.
Back then, we could still use functional components, but we couldn't tap into these lifecycle methods. They were called dumb components because they were just used to render UI.
Starting in React 16.8, we can now use functional components to tap into these lifecycle methods. We do this with hooks.
Ví dụ về 3 trạng thái của Component Lifecycycle
Dễ hiểu nhất khi đọc Class Component, nhưng dễ viết thì lại là Functional Component.
useEffect
Tại sao phải dùng useEffect để setlocalStorage.setItem mà không setItem ngay bên trong handleSave chẳng hạn?
Nếu bạn set localStorage ngay trong handleSave :
Thì vấn đề là:
Nếu bạn dùng useEffect
Đây gọi là “derive side effects from state”. Nghĩa là: cứ coi state là trung tâm, còn việc “phụ” (lưu storage, gọi API, đổi document.title…) thì để useEffect lo.
Vậy nên React mới khuyên: những việc side effect như localStorage, fetch API, setTimeout… nên bỏ vào useEffect thay vì cài chỗ này chỗ kia.
Nhớ kỹ 3 dạng dependency phổ biến
[] chạy duy nhất 1 lần khi mount (thường cho fetch API, addEventListener, init lib).
[state/props] chạy lại khi có thay đổi (document.title, localStorage sync).
Không có dependency: chạy mỗi lần render (hiếm khi cần, dễ tốn hiệu năng).
Việc “bên ngoài React” (side effect) là gì?
Là những việc không chỉ ảnh hưởng đến UI trong React, mà còn ảnh hưởng đến môi trường bên ngoài.
Ví dụ:
Tất cả những cái này vượt ra ngoài phạm vi React render → React gọi chung là side effects.
Tại sao phải tách ra useEffect?
Nếu bạn nhét side effect vào thẳng trong function component, thì:
useEffect sinh ra để:
Ví dụ sai và đúng
Gọi API trực tiếp trong render functional component
Ứng dụng phổ biến của useEffect
Đồng bộ state với localStorage
useEffect(() => {
localStorage.setItem("notes", JSON.stringify(notes));
}, [notes]);
Fetch API / gọi server
Khi component mount, gọi API để lấy dữ liệu ban đầu:
useEffect(() => {
fetch("https://api.example.com/data")
.then(res => res.json())
.then(data => setData(data));
}, []); // [] để chỉ chạy 1 lần khi mount
Đăng ký / gỡ bỏ event listener
Ví dụ: lắng nghe sự kiện resize window, scroll, keydown…
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []); // đăng ký 1 lần, cleanup khi unmount
Cập nhật document title, meta, URL…
useEffect(() => {
document.title = `Bạn có ${count} thông báo mới`;
}, [count]);
Kết nối Websocket / Real-time
useEffect(() => {
const socket = new WebSocket("wss://example.com");
socket.onmessage = (msg) => console.log(msg.data);
return () => socket.close(); // cleanup khi unmount
}, []);
Set interval / timeout
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c - 1);
}, 1000);
return () => clearInterval(timer); // cleanup khi component unmount
}, []);
Đồng bộ dữ liệu với các lib ngoài React
Ví dụ: tích hợp bản đồ (Leaflet, Google Maps), chart (Chart.js), hoặc thư viện UI (Bootstrap tooltip, jQuery plugin…).
useEffect(() => {
const chart = new Chart(document.getElementById("myChart"), config);
return () => chart.destroy();
}, []);
useEffect & Local Storage
I want to perform a side effect when the notes are updated: save the notes to local storage.
I can do this by using the `useEffect` hook. If we were using class components, we would use the `componentDidUpdate` lifecycle method.
Remember, the useEffect runs when the component mounts regardless of the dependencies.
In this case, the useEffect will run when the component mounts and when the notes change.
You can open your devtools and click on the Application tab. You will see the notes in local storage when you submit.
However when you referesh the page, the notes will disappear. Even from local storage.
This may be confusing. What's happening is when we refresh the page, the notes are being reset to the initial state. And remember, the useEffect runs when the notes are updated. So we are essentially overwriting the notes in local storage with the initial state.
However, there is an issue with this because setNotes is asynchronous.
This means that the notes will not be loaded in time for the useEffect to save the notes to local storage. This will cause the notes to be overwritten with the initial state.
To fix this, we can make the initial state from the `useState` hook a function that returns the notes from local storage.
We are using a function to set the initial state. This function will run once when the component mounts.
This will allow us to load the notes from local storage before the `useEffect` hook runs. Now the notes will be loaded from local storage when the component mounts and saved to local storage when the notes change.
useRef
It allows us to reference any DOM element directly, and we can then use the reference to access or manipulate that element — for example, setting focus to an input, reading its value, or even measuring its dimensions without triggering a re-render.
It can also be used to persist values across renders without causing a re-render.
useRef và useState
Giả dụ chúng ta cần xây dựng một Timer App có nút Stop và Reset.
Thì dưới đây là 2 ví dụ hoạt động giống nhau, chỉ khác nhau ở những chỗ rất nhỏ, mà khuyến nghị là nên dùng useRef để giảm bớt rủi ro.
