Các vấn đề thường gặp khi dùng useEffect trong ReactJs phần 2

Tiếp tục với series:”Các vấn đề thường gặp khi dùng useEffect trong ReactJs” thì chúng ta sẽ tiếp tục tìm hiểu thêm các vấn đề khác trong phần 2 này nhé.

Thiếu dependencies:

Nếu không chỉ định dependencies array, useEffect sẽ được gọi mỗi khi component render lại, gây ra hiệu suất không tối ưu hoặc các tác vụ không cần thiết. Hãy chắc chắn rằng bạn chỉ định dependencies array và cung cấp tất cả các biến phụ thuộc mà useEffect cần theo dõi.

Dưới đây là một ví dụ về việc thiếu dependencies trong useEffect:

import React, { useEffect, useState } from 'react';

const MissingDependenciesExample = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Effect triggered');
    setCount(count + 1);
  }, []); // Thiếu dependencies ở đây

  return (
    <div>
      <p>Count: {count}</p>
    </div>
  );
};

export default MissingDependenciesExample;

Trong ví dụ trên, useEffect không có dependencies được chỉ định (mảng dependencies rỗng []). Điều này có nghĩa là useEffect chỉ được gọi một lần sau khi component được render lần đầu tiên. Khi component render lại, useEffect sẽ không được gọi lại, dẫn đến việc state count không thay đổi.

Trong trường hợp này, nếu bạn muốn useEffect được gọi lại khi count thay đổi, bạn cần chỉ định count là dependencies bằng cách truyền nó vào mảng dependencies:

useEffect(() => {
  console.log('Effect triggered');
  setCount(count + 1);
}, [count]); // Chỉ định dependencies là count

Bằng cách này, useEffect sẽ được gọi lại mỗi khi count thay đổi, đảm bảo rằng setCount sẽ được gọi và state count sẽ được cập nhật khi count thay đổi.

Sự cố với cleanup:

useEffect cho phép bạn trả về một hàm cleanup để dọn dẹp khi component unmount hoặc khi dependencies thay đổi. Tuy nhiên, nếu không trả về hàm cleanup, hoặc trả về một hàm cleanup không đúng, có thể gây ra sự cố hoặc rò rỉ bộ nhớ. Hãy chắc chắn rằng bạn xử lý cleanup đúng cách để tránh vấn đề này.

Dưới đây là một ví dụ về sự cố có thể xảy ra khi không xử lý đúng cleanup trong useEffect:

import React, { useEffect, useState } from 'react';

const CleanupIssueExample = () => {
  const [isVisible, setIsVisible] = useState(true);

  useEffect(() => {
    console.log('Effect triggered');

    return () => {
      console.log('Cleanup');
      // Thực hiện một số công việc cleanup, chẳng hạn là hủy đăng ký sự kiện
      // ...
    };
  }, []);

  const toggleVisibility = () => {
    setIsVisible(!isVisible);
  };

  return (
    <div>
      {isVisible && <p>Hello, World!</p>}
      <button onClick={toggleVisibility}>Toggle Visibility</button>
    </div>
  );
};

export default CleanupIssueExample;

Trong ví dụ trên, chúng ta sử dụng useEffect để đăng ký một công việc cleanup bằng cách trả về một hàm từ trong useEffect. Trong trường hợp này, công việc cleanup có thể là hủy đăng ký sự kiện hoặc dọn dẹp các tài nguyên không cần thiết khác.

Tuy nhiên, trong ví dụ trên, khi component unmount hoặc dependencies thay đổi, useEffect sẽ được gọi lại và hàm cleanup sẽ được gọi. Nhưng vấn đề là, mỗi lần component render lại, một hàm cleanup mới được tạo ra và hàm cleanup cũ không được gọi, dẫn đến việc gây rò rỉ bộ nhớ hoặc vấn đề khác liên quan đến tài nguyên không được giải phóng.

Để khắc phục vấn đề này, chúng ta cần xử lý cleanup đúng cách bằng cách đảm bảo rằng hàm cleanup chỉ được gọi khi component unmount hoặc dependencies thay đổi. Trong trường hợp này, ta cần chỉ định dependencies trong mảng dependencies của useEffect. Ví dụ, nếu ta cần chỉ định isVisible là dependencies, chúng ta có thể sửa đổi như sau:

useEffect(() => {
  console.log('Effect triggered');

  return () => {
    console.log('Cleanup');
    // Thực hiện một số công việc cleanup, chẳng hạn là hủy đăng ký sự kiện
    // ...
  };
}, [isVisible]); // Chỉ định dependencies là isVisible

Khi isVisible thay đổi, useEffect sẽ được gọi lại và hàm cleanup sẽ được gọi trước khi công việc useEffect mới được thực hiện. Điều này đảm bảo rằng hàm cleanup sẽ được gọi đúng lúc và giúp giải phóng tài nguyên một cách chính xác.

Thực hiện không đồng bộ:

Một số tác vụ trong useEffect có thể mất thời gian để hoàn thành, chẳng hạn như gọi API. Nếu không xử lý đúng, có thể gây ra vấn đề về hiệu suất và trải nghiệm người dùng. Sử dụng async/await hoặc Promise để thực hiện các tác vụ không đồng bộ trong useEffect và xử lý lỗi một cách đúng đắn.

Dưới đây là một ví dụ về việc thực hiện tác vụ không đồng bộ trong useEffect, sử dụng async/await:

import React, { useEffect, useState } from 'react';

const AsyncTaskExample = () => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      try {
        // Giả định gọi API không đồng bộ
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();

        setData(data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }

      setIsLoading(false);
    };

    fetchData();
  }, []);

  return (
    <div>
      {isLoading ? (
        <p>Loading...</p>
      ) : (
        <div>
          {data ? (
            <p>Data: {data}</p>
          ) : (
            <p>No data available</p>
          )}
        </div>
      )}
    </div>
  );
};

export default AsyncTaskExample;

Trong ví dụ trên, chúng ta sử dụng useEffect để thực hiện một tác vụ không đồng bộ, trong trường hợp này là gọi API để lấy dữ liệu từ máy chủ. Chúng ta sử dụng async/await để đảm bảo rằng các yêu cầu mạng được thực hiện một cách không đồng bộ và không chặn luồng chính.

Trong hàm callback của useEffect, chúng ta khai báo một hàm fetchData là một async function. Trong fetchData, trước khi thực hiện yêu cầu mạng, chúng ta đặt isLoading thành true để hiển thị thông báo “Loading…” trên giao diện.

Sau đó, chúng ta sử dụng try/catch để xử lý lỗi trong quá trình gọi API. Nếu có lỗi, chúng ta in ra thông báo lỗi và không cập nhật state data. Nếu không có lỗi, chúng ta lấy dữ liệu từ phản hồi API và cập nhật state data bằng setData.

Cuối cùng, chúng ta đặt isLoading thành false để ẩn thông báo “Loading…” và hiển thị dữ liệu đã lấy được (nếu có) hoặc thông báo “No data available” (nếu không có dữ liệu).

Với cách làm này, khi component được render lần đầu tiên, useEffect sẽ gọi fetchData để lấy dữ liệu từ API mà không chặn luồng chính. Khi dữ liệu được nhận và state data được cập nhật, component sẽ render lại với dữ liệu mới và hiển thị lên giao diện.

Xử lý lỗi:

Nếu có lỗi xảy ra trong useEffect, nó sẽ không được ghi lại hoặc xử lý tự động. Bạn cần tự xử lý và bắt các lỗi trong useEffect để tránh tình trạng bị crash hoặc hiển thị lỗi không mong muốn trên giao diện người dùng.

Dưới đây là một ví dụ về việc xử lý lỗi trong useEffect để tránh tình trạng crash và hiển thị lỗi không mong muốn trên giao diện người dùng:

import React, { useEffect, useState } from 'react';

const ErrorHandlingExample = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        
        if (!response.ok) {
          throw new Error('Error fetching data');
        }
        
        const data = await response.json();
        setData(data);
      } catch (error) {
        console.error('Error:', error);
        // Xử lý lỗi ở đây, ví dụ: hiển thị thông báo lỗi cho người dùng
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? (
        <p>Data: {data}</p>
      ) : (
        <p>No data available</p>
      )}
    </div>
  );
};

export default ErrorHandlingExample;import React, { useEffect, useState } from 'react';

const ErrorHandlingExample = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        
        if (!response.ok) {
          throw new Error('Error fetching data');
        }
        
        const data = await response.json();
        setData(data);
      } catch (error) {
        console.error('Error:', error);
        // Xử lý lỗi ở đây, ví dụ: hiển thị thông báo lỗi cho người dùng
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {data ? (
        <p>Data: {data}</p>
      ) : (
        <p>No data available</p>
      )}
    </div>
  );
};

export default ErrorHandlingExample;

Trong ví dụ trên, chúng ta sử dụng try/catch để bắt các lỗi có thể xảy ra trong useEffect. Trong hàm fetchData, chúng ta thực hiện yêu cầu mạng và kiểm tra xem phản hồi từ API có thành công hay không (response.ok). Nếu phản hồi không thành công, chúng ta tung ra một lỗi bằng cách sử dụng throw new Error('Error fetching data').

Trong khối catch, chúng ta xử lý lỗi bằng cách in ra thông báo lỗi trong console (console.error('Error:', error)) và thực hiện các hành động xử lý lỗi khác nhau, ví dụ như hiển thị thông báo lỗi cho người dùng thông qua một component thông báo lỗi.

Bằng cách này, chúng ta đảm bảo rằng các lỗi trong useEffect sẽ được bắt và xử lý. Chúng ta có thể hiển thị thông báo lỗi cho người dùng hoặc thực hiện các hành động phù hợp để khắc phục tình huống lỗi một cách điều khiển và không để ứng dụng crash hoặc hiển thị lỗi không mong muốn trên giao diện người dùng.

Trên là những vấn đề mà mình gặp phải khi sử dụng useEffect trong quá trình làm việc và đưa ra các giải pháp để mọi người tham khảo. Nếu có ý kiến bổ sung về bài viết xin hãy comment phía dưới để chúng ta cùng hoàn thiện hơn.

Cám ơn mọi người đã theo dõi bài viết của mình <3.

Related Posts