简体   繁体   中英

How to force a re-render of a hook from a parent component in react?

I have two files:

  1. App.js
  2. useFetch.js

useFetch.js is a custom hook that i want to use to do requests to certain API using fetch.

This is the content of App.js:

import React, { useState } from 'react';
import useFetch from './hooks/useFetch';
...
const App = () => {
  const [page, setPage] = useState(1);
  const [data, error, setUrl, isLoading, abort] = useFetch();

  const handleAction = () => {
    if (isLoading) {
      abort();
      console.log('abort signal sended');
    } else {
      setUrl(`https://reqres.in/api/users?page=${page}`);
    }
  };

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div
          className="App-header-button"
        >
          <Button
            variant="contained"
            color={isLoading ? 'secondary' : 'primary'}
            onClick={() => handleAction()}
          >
            {isLoading ? 'Cancel' : 'Test'}
          </Button>
        </div>
        {data && 'Data loaded'}
        <input type="text" onChange={event => setPage(event.target.value)} value={page} />
      </header>
    </div>
  );
};

export default App;

As you can see, is a simple app. The input on the end is using to set the page to display from the API.

This is the content of my custom useFetch.js:

import { useState, useEffect } from 'react';

  const useFetch = (defaultUrl) => {
  const [url, setUrl] = useState(defaultUrl);
  const [data, setData] = useState();
  const [error, setError] = useState();
  const [isLoading, setIsLoading] = useState(false);
  const [controller] = useState(new AbortController());

  const { signal } = controller;

  useEffect(() => {
    if (url) {
      setIsLoading(true);
      fetch(url, { signal })
        .then(res => res.json())
        .then((res) => {
          setData(res.data);
          setIsLoading(false);
        })
        .catch((err) => {
          setError(err);
          console.log(err.name);
          setIsLoading(false);
        });
    }
  }, [url]);

  const abort = () => {
    controller.abort();
    setIsLoading(false);
  };

  return [
    data,
    error,
    setUrl,
    isLoading,
    abort,
  ];
};

export default useFetch;

I have one problem here... this works very good and display the data when i change the page and hit the button. The problem is: If i push the button with the page and hit the button again with the same page, nothing happens. This have certain sense because the url aren't change, still be the same data sended to the hook. Suppose the user wants to refresh the data with the same page hitting the button again. On this case i need the hook makes again the same call repeating the same url. So how can I do to force the hook to 'render' it again?

[UPDATE I]

With the help of @Atul i made this changes and works. I only wonder if this is a good way or the most appropriate way to achieve this.

On useFetch.js:

  useEffect(() => {
    if (url && refreshFlag) {
      setIsLoading(true);
      fetch(url, { signal })
        .then(res => res.json())
        .then((res) => {
          setData(res.data);
          setIsLoading(false);
          setRefreshFlag(false);
        })
        .catch((err) => {
          setError(err);
          console.log(err.name);
          setRefreshFlag(false);
          setIsLoading(false);
        });
    }
  }, [url, refreshFlag]);

On App.js: (only showing the code of the button action)

  const handleAction = () => {
    if (isLoading) {
      abort();
      console.log('abort signal sended');
    } else {
      refresh(true);
      setUrl(`https://reqres.in/api/users?page=${page}`);
    }
  };

I know that @Atul alread gave you a solution to this problem,
and I used this to also make things work in my code,
but not all is said here and I needed to think about it a bit, how to implement it,

So I'll write this answer to make this simpler for other guys which will visit this page.
Keep in mind that credits goes to @Atul :)

I will list what changes needs to be done in code above to make it work.

First, in useFetch.ts , you want to:

  1. add new state
    const [refreshFlag, setRefreshFlag] = useState(true)

  2. add a function which triggers the refresh
    const refresh = () => {setRefreshFlag(true)}

  3. then you want to 'export' this function, not the state, so the return part of useFetch.ts should look like this:

  return [
    data,
    error,
    setUrl,
    isLoading,
    abort,
    refresh, // added this
  ];
  1. You want your useEffect to depend on the refreshFlag , and you already have that.
    One thing to change to make the code nicer is, instead of doing setIsLoading(false); and setRefreshFlag(false); in both try and catch , you can insted do it one time - in finally

Now, in App.js :

A) You want to use refresh() in place where you have refresh(true);

B) You haven't put in the code above that you also need to destructure refresh from useFetch , so change
const [data, error, setUrl, isLoading, abort] = useFetch(); to
const [data, error, setUrl, isLoading, abort, refresh] = useFetch();

--------------- Below is a tip not realated to answer, -------------------
--------------- just something what imo makes the code cleaner ------------

Something to add - this is a matter of taste, but I personally dont like destructuring things like you did from useFetch . Reasons are:

  • when you will have two API requests in one component, you will need to create unique names for variables from those hooks
  • when you see in code refresh you don't know that it's the function from API, you need to spend time (few seconds, but always something) to get that info

Instead what I do is make a variable for the hook inside component:
const fetch = useFetch()
(this useFetch name is bad example, in practice you will have something like
const getUsers = useGetUsers() ),
and then you will use, in your code, getUsers.data , getUsers.isLoading etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM