I have two files:
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:
add new stateconst [refreshFlag, setRefreshFlag] = useState(true)
add a function which triggers the refreshconst refresh = () => {setRefreshFlag(true)}
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
];
useEffect
to depend on the refreshFlag
, and you already have that.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();
toconst [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:
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 infoInstead 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.