thắc mắc [ReactJS] Re-render component sau khi update state Redux

Hello.World

Senior Member
hi các bác, e đang tự học và đang làm 1 website bán hàng: database dùng mongodb, backend e build đơn giản bằng nodejs, frontend thì e làm bằng reactjs + redux toolkit.
e đang làm phần admin board để quản trị user, product, categories gồm mấy chức năng cơ bản như xem, thêm, sửa, xoá. Đến phần update thì e gặp vấn đề:

1. Khi submit dispatch actions thành công và server đã cập nhật và trả về data nhưng react lại không re-render để cập nhật state từ store của redux.

2. Bình thường nếu vào từ pages Categories để xem danh sách danh mục rồi thêm, sửa hoặc xoá danh mục thì không có vấn đề, do data đã được get từ page trước đó là Categories. Nhưng khi vào thẳng link Add hoặc Update Category, ví dụ như: "http://localhost:3000/categories/635b59d7934596f6fb3af548", do k đc get đc data trước đó nên trong mỗi pages Add hoặc Update Category e đều phải chạy hàm getCategories() để get data, có cách nào khắc phục vấn đề này không ạ?

E đã google thử khá nhiều cách nhưng chưa giải quyết được. Văn e hơi lủng củng mong các bác giúp đỡ. Ở dưới là đoạn code của page UpdateCategory, do e thấy page AddCategory và UpdateCagegory chung form nên e tạo 1 form component r truyền thẳng state xuống đó luôn.

JavaScript:
import FormCategory from "../../../components/FormCategory/FormCategory";
import "./update-category.scss";
import { useEffect, useState } from "react";
import storage from "../../../firebase";
import { ref, uploadBytesResumable, getDownloadURL, deleteObject } from "firebase/storage";
import { getCategories, updateCategory } from "../../../redux/apiCalls";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";

const UpdateCategory = () => {
  const { id } = useParams();
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [inputs, setInputs] = useState({
    name: '',
    desc: '',
    slug: '',
    isActive: false
  });
  const [file, setFile] = useState({});
  const [tempURL, setTempURL] = useState("");
  const categories = useSelector((state) => state.category.categories);
  const category = categories.filter(item => item._id === id)[0];

  useEffect(() => {
    getCategories(dispatch);
    if (category) {
      setInputs({
        name: category.name,
        desc: category.desc,
        slug: category.slug,
        isActive: category.isActive
      });
      setTempURL(category.img);
    }
  }, [categories.length, dispatch]);

  const handleOnChange = (e) => {
    if (e.target.type === "checkbox") {
      setInputs({ ...inputs, [e.target.name]: e.target.checked });
    } else {
      setInputs({ ...inputs, [e.target.name]: e.target.value });
    }
  };

  const updateImage = () => {
    return new Promise((resolve, reject) => {
      const fileName = new Date().getTime() + file.name;
      const storageRef = ref(storage, fileName);
      const uploadTask = uploadBytesResumable(storageRef, file);

      // Register three observers:
      // 1. 'state_changed' observer, called any time the state changes
      // 2. Error observer, called on failure
      // 3. Completion observer, called on successful completion
      uploadTask.on('state_changed',
        (snapshot) => {
          // Observe state change events such as progress, pause, and resume
          // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
          const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        },
        (error) => {
          // Handle unsuccessful uploads
        },
        async () => {
          // Handle successful uploads on complete
          // For instance, get the download URL: https://firebasestorage.googleapis.com/...
          const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
          console.log(downloadURL);
          resolve(downloadURL);
          delImgFireBase();
        }
      );
    });
  };

  const delImgFireBase = () => {
    // Create a reference to the file to delete
    const desertRef = ref(storage, category.img);
    console.log("category img", category.img)
    // Delete the file
    deleteObject(desertRef).then(() => {
      console.log("xoa thanh cong", category.img);
    }).catch((error) => {
      console.error(error);
    });

  }

  const handleOnSubmit = async () => {
    const imgURL = tempURL ? tempURL : await updateImage();
    const updatedCategory = {
      ...inputs,
      img: imgURL,
    };
    updateCategory(dispatch, id, updatedCategory);
  };

  const handleCancel = () => {
    navigate("/categories");
  }
  return (
    <div className="update-category">
      <FormCategory
        file={file}
        setFile={setFile}
        tempURL={tempURL}
        setTempURL={setTempURL}
        inputs={inputs}
        handleOnChange={handleOnChange}
        handleOnSubmit={handleOnSubmit}
        handleCancel={handleCancel}
      />
    </div>
  );
};

export default UpdateCategory;

2 function getCategories và updateCategory trong apiCalls:
JavaScript:
export const getCategories = async (dispatch) => {
  dispatch(getCategoryStart());
  try {
    const res = await userRequest.get("/category/");
    dispatch(getCategorySucces(res.data));
  } catch {
    dispatch(getCategoryFailure());
  }
}

export const updateCategory = async (dispatch, id, category) => {
  dispatch(updateCategoryStart());
  try {
    const res = await userRequest.put(`/category/${id}`, category);
    dispatch(updateCategorySucces(res.data));
  } catch {
    dispatch(updateCategoryFailure());
  }
}
 
Last edited:
1. Mình ko chắc sao nó ko render lại nhưng bro nên call dispatch trong scope của Component nha, đừng pass qua function rồi call thế kia.
2. Tui thấy ko có cách nhé, ko có data thì phải fetch data từ server, chỉ có cách trick như lưu data xuống localstorage -> lúc cần thì load lên. Hoặc làm 1 cái loading screen wrapper lại tất cả component, thằng này sẽ load data -> lưu vào redux storage rồi mới hiện lên component
Note: tui thấy bro đang cần check khi nào request start/end/faill thì có thể dùng thằng này https://swr.vercel.app/
 
1. Mình ko chắc sao nó ko render lại nhưng bro nên call dispatch trong scope của Component nha, đừng pass qua function rồi call thế kia.
2. Tui thấy ko có cách nhé, ko có data thì phải fetch data từ server, chỉ có cách trick như lưu data xuống localstorage -> lúc cần thì load lên. Hoặc làm 1 cái loading screen wrapper lại tất cả component, thằng này sẽ load data -> lưu vào redux storage rồi mới hiện lên component
Note: tui thấy bro đang cần check khi nào request start/end/faill thì có thể dùng thằng này https://swr.vercel.app/
tks bác. Chỗ bôi đậm này nếu dùng như e thì thường sẽ phát sinh vấn đề gì ạ?
 
Hàn get và update của bạn là action creator đúng ko? Nếu vậy bạn phải dùng middleware để handle async action vì bản thân thằng action creators là syn
 
tks bác. Chỗ bôi đậm này nếu dùng như e thì thường sẽ phát sinh vấn đề gì ạ?
Mình cũng ko chắc nó sẽ phát sinh gì, nhưng thường mình ko viết vậy và team của mình cũng ko ai viết vậy :)))). Theo mình thì hàm fetch data thì việc của nó là fetch data/raise error thôi, việc dispatch vậy nó tạo ra side effect trong hàm fetch
Ví dụ như có 1 component khác cũng cần fetch categories nhưng lại ko cần dispatch data chẳng hạn, nếu dùng như trên thì sẽ ko thể reuse lại đc hàm fetch data
 
Hàn get và update của bạn là action creator đúng ko? Nếu vậy bạn phải dùng middleware để handle async action vì bản thân thằng action creators là syn
cám ơn bác. E đính chính lại e console.log state trong action creators thì th categories bị lỗi thật =((


Mình cũng ko chắc nó sẽ phát sinh gì, nhưng thường mình ko viết vậy và team của mình cũng ko ai viết vậy :)))). Theo mình thì hàm fetch data thì việc của nó là fetch data/raise error thôi, việc dispatch vậy nó tạo ra side effect trong hàm fetch
Ví dụ như có 1 component khác cũng cần fetch categories nhưng lại ko cần dispatch data chẳng hạn, nếu dùng như trên thì sẽ ko thể reuse lại đc hàm fetch data
cám ơn bác. nếu bt thì ng ta sẽ code kiểu này luôn à bác?
JavaScript:
useEffect(() => {
    // getCategories(dispatch);
    const fetchData = async () => {
      const res = await userRequest.get(`/category/${id}`);
      dispatch(getCurrentCategory(res.data));
    };
    fetchData();
    if (category) {
      setInputs({
        name: category.name,
        desc: category.desc,
        slug: category.slug,
        isActive: category.isActive
      });
      setTempURL(category.img);
    }
    console.log("useEffect", category);
  }, [dispatch, category.name]);
 
Last edited:
1. Lười viết quá nên để tạm cái hình và mấy bài viết thím tự đọc về cách xử lý side effect khi follow redux nhé, mấy cái fetch với update có vẻ chưa đúng rồi


1667063152159.png



https://stackoverflow.com/questions...are-for-async-flow-in-redux/34599594#34599594
https://redux.js.org/introduction/learning-resources#side-effects---basics

2. xem thử bài này https://redux.js.org/tutorials/essentials/part-6-performance-normalization#normalizing-data.
 
Last edited:
JavaScript:
useEffect(() => {
    const fetchData = async () => {
        const res = await userRequest.get(`/category/${id}`);
        dispatch(getCurrentCategory(res.data));
    };
    fetchData();
}, []);

useEffect(() => {
    if (category) return;
    setInputs({
        name: category.name,
        desc: category.desc,
        slug: category.slug,
        isActive: category.isActive
    });
    setTempURL(category.img);
}, [category]);

Cơ bản thì nó sẽ thế này, còn thực tế thì tùy project nữa
 
1. Lười viết quá nên để tạm cái hình và mấy bài viết thím tự đọc về cách xử lý side effect khi follow redux nhé, mấy cái fetch với update có vẻ chưa đúng rồi




2. xem thử bài này https://redux.js.org/tutorials/essentials/part-6-performance-normalization#normalizing-data.
cám ơn bác, e ngâm cứu lại ạ, trước e cứ nghĩ phần asynchronus dễ, sau động vào lỗi tè le hột me =((

JavaScript:
useEffect(() => {
    const fetchData = async () => {
        const res = await userRequest.get(`/category/${id}`);
        dispatch(getCurrentCategory(res.data));
    };
    fetchData();
}, []);

useEffect(() => {
    if (category) return;
    setInputs({
        name: category.name,
        desc: category.desc,
        slug: category.slug,
        isActive: category.isActive
    });
    setTempURL(category.img);
}, [B][category][/B]);

Cơ bản thì nó sẽ thế này, còn thực tế thì tùy project nữa
cám ơn bác nhiều ạ :love:. phần bôi đậm s e cho array hoặc object vào depency của useEffect nó toàn bị re-render infinite ạ.
 
hi các bác, e đang tự học và đang làm 1 website bán hàng: database dùng mongodb, backend e build đơn giản bằng nodejs, frontend thì e làm bằng reactjs + redux toolkit.
e đang làm phần admin board để quản trị user, product, categories gồm mấy chức năng cơ bản như xem, thêm, sửa, xoá. Đến phần update thì e gặp vấn đề:

1. Khi submit dispatch actions thành công và server đã cập nhật và trả về data nhưng react lại không re-render để cập nhật state từ store của redux.

2. Bình thường nếu vào từ pages Categories để xem danh sách danh mục rồi thêm, sửa hoặc xoá danh mục thì không có vấn đề, do data đã được get từ page trước đó là Categories. Nhưng khi vào thẳng link Add hoặc Update Category, ví dụ như: "http://localhost:3000/categories/635b59d7934596f6fb3af548", do k đc get đc data trước đó nên trong mỗi pages Add hoặc Update Category e đều phải chạy hàm getCategories() để get data, có cách nào khắc phục vấn đề này không ạ?

E đã google thử khá nhiều cách nhưng chưa giải quyết được. Văn e hơi lủng củng mong các bác giúp đỡ. Ở dưới là đoạn code của page UpdateCategory, do e thấy page AddCategory và UpdateCagegory chung form nên e tạo 1 form component r truyền thẳng state xuống đó luôn.

JavaScript:
import FormCategory from "../../../components/FormCategory/FormCategory";
import "./update-category.scss";
import { useEffect, useState } from "react";
import storage from "../../../firebase";
import { ref, uploadBytesResumable, getDownloadURL, deleteObject } from "firebase/storage";
import { getCategories, updateCategory } from "../../../redux/apiCalls";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";

const UpdateCategory = () => {
  const { id } = useParams();
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const [inputs, setInputs] = useState({
    name: '',
    desc: '',
    slug: '',
    isActive: false
  });
  const [file, setFile] = useState({});
  const [tempURL, setTempURL] = useState("");
  const categories = useSelector((state) => state.category.categories);
  const category = categories.filter(item => item._id === id)[0];

  useEffect(() => {
    getCategories(dispatch);
    if (category) {
      setInputs({
        name: category.name,
        desc: category.desc,
        slug: category.slug,
        isActive: category.isActive
      });
      setTempURL(category.img);
    }
  }, [categories.length, dispatch]);

  const handleOnChange = (e) => {
    if (e.target.type === "checkbox") {
      setInputs({ ...inputs, [e.target.name]: e.target.checked });
    } else {
      setInputs({ ...inputs, [e.target.name]: e.target.value });
    }
  };

  const updateImage = () => {
    return new Promise((resolve, reject) => {
      const fileName = new Date().getTime() + file.name;
      const storageRef = ref(storage, fileName);
      const uploadTask = uploadBytesResumable(storageRef, file);

      // Register three observers:
      // 1. 'state_changed' observer, called any time the state changes
      // 2. Error observer, called on failure
      // 3. Completion observer, called on successful completion
      uploadTask.on('state_changed',
        (snapshot) => {
          // Observe state change events such as progress, pause, and resume
          // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
          const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        },
        (error) => {
          // Handle unsuccessful uploads
        },
        async () => {
          // Handle successful uploads on complete
          // For instance, get the download URL: https://firebasestorage.googleapis.com/...
          const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
          console.log(downloadURL);
          resolve(downloadURL);
          delImgFireBase();
        }
      );
    });
  };

  const delImgFireBase = () => {
    // Create a reference to the file to delete
    const desertRef = ref(storage, category.img);
    console.log("category img", category.img)
    // Delete the file
    deleteObject(desertRef).then(() => {
      console.log("xoa thanh cong", category.img);
    }).catch((error) => {
      console.error(error);
    });

  }

  const handleOnSubmit = async () => {
    const imgURL = tempURL ? tempURL : await updateImage();
    const updatedCategory = {
      ...inputs,
      img: imgURL,
    };
    updateCategory(dispatch, id, updatedCategory);
  };

  const handleCancel = () => {
    navigate("/categories");
  }
  return (
    <div className="update-category">
      <FormCategory
        file={file}
        setFile={setFile}
        tempURL={tempURL}
        setTempURL={setTempURL}
        inputs={inputs}
        handleOnChange={handleOnChange}
        handleOnSubmit={handleOnSubmit}
        handleCancel={handleCancel}
      />
    </div>
  );
};

export default UpdateCategory;

2 function getCategories và updateCategory trong apiCalls:
JavaScript:
export const getCategories = async (dispatch) => {
  dispatch(getCategoryStart());
  try {
    const res = await userRequest.get("/category/");
    dispatch(getCategorySucces(res.data));
  } catch {
    dispatch(getCategoryFailure());
  }
}

export const updateCategory = async (dispatch, id, category) => {
  dispatch(updateCategoryStart());
  try {
    const res = await userRequest.put(`/category/${id}`, category);
    dispatch(updateCategorySucces(res.data));
  } catch {
    dispatch(updateCategoryFailure());
  }
}
Sao tôi thấy ông trong useEffecr nhẽ ra chỉ cần getcategories() thôi chứ k cần tham số dispatch. Với cả phần actions sao t thấy dispatch cái categorystart có vẻ thừa nếu nó k phải loading. Với cả nếu dùng như này nên dùng createasyncthunk sẽ dễ hơn
 
Sao tôi thấy ông trong useEffecr nhẽ ra chỉ cần getcategories() thôi chứ k cần tham số dispatch. Với cả phần actions sao t thấy dispatch cái categorystart có vẻ thừa nếu nó k phải loading. Với cả nếu dùng như này nên dùng createasyncthunk sẽ dễ hơn
tks ông. Trước project để ông đi xin intern/fresher ông dùng những công nghệ gì thế, redux, thunk, saga,... áp dụng vào luôn à ?
 
tks ông. Trước project để ông đi xin intern/fresher ông dùng những công nghệ gì thế, redux, thunk, saga,... áp dụng vào luôn à ?
Uh, nhanh mà đi làm fence ah. Nắm được luồng, hiểu dc những thành phần chính là dc r, k quan trọng quá đâu, project lúc phỏng vấn chỉ xem qua thôi
 
Back
Top