I'm trying to make reusable GET
and POST
requests, but I'm getting error Invalid hook call
when setting the data
state in the reusable fetch request component. I've just learnt that we can only use hooks in React components, so how can I make that functionality reusable?
import axios from "axios";
import { useContext, useEffect, useState } from "react";
import AuthContext from "../../context/Auth-context/AuthContext";
import { useNavigate } from "react-router-dom";
export const POST = (url, body, credentials) => {
const [data, setData] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
const { dispatch } = useContext(AuthContext);
const navigate = useNavigate();
useEffect(() => {
const fetchData = () => {
axios
.post(url, body, { withCredentials: credentials })
.then((response) => {
setIsLoaded(true);
setData(response.data);
})
.catch((error) => {
if (error.status === 401) {
dispatch({ type: "LOGOUT" });
navigate("/login");
return;
}
setError(error);
});
};
fetchData();
}, [url, credentials, dispatch, navigate, body]);
return { error, isLoaded, data };
};
const approve = async (id) => {
const { data, error, isLoaded } = POST(
"http://localhost:8000/api/new-users",
id,
true
);
if (error) {
return (
<Error/>
);
}
if (!isLoaded) {
return (
<Loader/>
);
}
console.log(data);
};
The code is breaking React's Rules of Hooks. React hooks, i.e. useState
, useNavigate
, etc, can only be called in React functions and custom React hooks, and POST
is neither of these. The trivial solution would be to simply rename POST
to be a valid React hook, e.g. using the "use-"
prefix on the identifier. The issue then is that you would be calling usePost
in a callback which again breaks React's Rules of Hooks.
I suggest a refactor to return a "trigger" function that handles the fetch (instead of useEffect
) which can be called in a callback.
Example:
export const usePost = (url, credentials) => {
const [data, setData] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
const { dispatch } = useContext(AuthContext);
const navigate = useNavigate();
const trigger = useCallback((body) => {
return axios
.post(url, body, { withCredentials: credentials })
.then((response) => {
setData(response.data);
})
.catch((error) => {
if (error.status === 401) {
dispatch({ type: "LOGOUT" });
navigate("/login");
return;
}
setError(error);
})
.finally(() => {
setIsLoaded(true);
});
}, [url, credentials, dispatch, navigate]);
return { error, isLoaded, data, trigger };
};
Usage:
const { data, error, isLoaded, trigger } = usePost(
"http://localhost:8000/api/new-users",
true
);
...
const approve = async (id) => {
trigger(id);
}
...
if (error) {
return <Error />;
}
if (!isLoaded) {
return <Loader />;
}
...