Home > OS >  My component keeps rerendering while using a path param
My component keeps rerendering while using a path param

Time:08-03

I'm trying to use a path param which I fetch from the URL path as an ID for an entity which I'm trying to fetch. I've created a custom data fetching hook which triggers when either the path of the passed params change. I'm use useParams from react router dom to get the ID of the book from the URL.

Below is the component code:

const BookDetails: FC<BookDetailsProps> = () => {
  let { bookId } = useParams();

  const { response, loading, error } = useApi(`/books/v1/volumes/${bookId}`);

  return <Wrapper></Wrapper>;
};

export default BookDetails;

And below is my custom hook:

const useApi = (url: string, params = {}) => {
  const [response, setResponse] = useState<AxiosResponse>();
  const [error, setError] = useState<AxiosError>();
  const [loading, setLoading] = useState(true);

  const fetchData = useCallback(async () => {
    setLoading(true);
    try {
      const result = await api(url, { params: params });
      setResponse(result);
    } catch (err: any) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [url, params]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { response, error, loading, fetchData };
};

export default useApi;

Now when I use my API hook with a component which fetches data using the params object everything is fine. The component doesn't rerender and I just get the data, but for some reason when I use it with path params it goes off.

Does anyone know how I should proceed?

CodePudding user response:

params gets a new object instance on each call so the dependency array in the useCallback will never be the same between different calls (you haven't pass a params argument to your useApi) and therefore the useEffect will be triggered on each render casing a re-render, hence the infinite rerendering

CodePudding user response:

I think the issue is that when no params object is passed to the useApi hook

useApi(`/books/v1/volumes/${bookId}`)

The params argument is initialized to a default empty object value.

const useApi = (url: string, params = {}) => {
  ...

This causes the params variable to be a new object reference anytime the component rerenders for any reason and will retrigger the useEffect hook because fetchData will be a newly recomputed reference.

  ...

  const fetchData = useCallback(async () => {
    setLoading(true);
    try {
      const result = await api(url, { params: params });
      setResponse(result);
    } catch (err: any) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [url, params]); // <-- params new reference

  useEffect(() => {
    fetchData();
  }, [fetchData]); // <-- fetchData becomes new reference

  ...
};

I suggest not initializing param and only provide a fallback value when calling the api function.

Example:

const useApi = (url: string, params) => { // <-- don't initialize
  const [response, setResponse] = useState<AxiosResponse>();
  const [error, setError] = useState<AxiosError>();
  const [loading, setLoading] = useState(true);

  const fetchData = useCallback(async () => {
    setLoading(true);
    try {
      const result = await api(url, { params: params || {} }); // <-- provide fallback value
      setResponse(result);
    } catch (err: any) {
      setError(err);
    } finally {
      setLoading(false);
    }
  }, [url, params]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return { response, error, loading, fetchData };
};

export default useApi;

This way params will be an undefined value from render to render and not change shallow reference equality each render.

  • Related