I made custom useFetch hook that is responsible for sending request to the API.
Hook returns object with isLoading state, error state and function that triggers fetch request.
Problem is when i use it in component and i trigger sendRequest function (which is returned by the hook), component doesent get latest errorState provided by the hook.
I kinda understand where is the problem but still cannot find solution.
Here is useFetch hook
const useFetch = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const sendRequest = async (
url: string,
requestConfig?: RequestInit | undefined
) => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(url, {
method: requestConfig ? requestConfig.method : "GET",
headers: requestConfig ? requestConfig.headers : {},
body: requestConfig ? JSON.stringify(requestConfig.body) : null,
});
if (!response.ok) throw new Error("Request failed!");
const data = response.json();
return data;
} catch (error: any) {
setError(error.message);
} finally {
setIsLoading(false);
}
};
return {
isLoading,
error,
sendRequest,
};
};
Here is component that use hook
type ContactProps = {
contact: TContact;
setContacts: React.Dispatch<React.SetStateAction<TContact[]>>;
};
function Contact({ contact, setContacts }: ContactProps) {
const { isLoading, error, sendRequest } = useFetch();
const deleteContactHandler = useCallback(async () => {
const response = await sendRequest(`${BASE_API_URL}/users/${contact.id}`, {
method: "DELETE",
});
console.log(response);
console.log(error);
if (!error) {
setContacts((prevContacts) => {
return prevContacts.filter((item) => item.id !== contact.id);
});
}
}, []);
return (
<li className={classes["contact-item"]}>
<figure className={classes["profile-picture-container"]}>
<img
src={contact.profilePicture}
alt={contact.name}
className={classes["profile-picture"]}
></img>
</figure>
<div className={classes["contact-info"]}>
<p>{contact.name}</p>
<p>{contact.phone}</p>
<p>{contact.email}</p>
</div>
<div className={classes["contact-actions"]}>
<Button
className={classes["actions-btn"]}
onClick={() => console.log("Dummy!!!")}
>
<BsPencilFill />
</Button>
<Button
className={classes["actions-btn"]}
onClick={deleteContactHandler}
>
<BsFillTrashFill />
</Button>
</div>
</li>
);
}```
2
Answers
Your
deleteContactHandler
is wrapped in auseCallback
which means that it will not get updated when state changes. If you have a linter this will probably we mentioned. Something likeSo to fix this you can add the dependencies.
But this will still not fix your problem since the error has still the previous value of
null
from when the function got called.I suggest you do some refactoring where you add a
data
state to your hook. And pass theurl
andmethod
when you initialize the hook.Which you can use like so
Of course you’ll want to set your
setContacts
with the new data. For that you can use auseEffect
.Also worth mentioning is that you’re not awaiting the
response.json()
.Here is a live preview.
Functionality that affects state should be wrapped in a
useCallback
hook.Here is a simplified working version of your example. You can see the error occur by changing
result.ok
to false inmockFetch
.