0
\$\begingroup\$

I created custom hooks, which allows to fetch the API using two request methods 'GET' and 'POST'.

Use cases:

const { data: fetchData, loading: fetchLoading, error: fetchError } = useFetch('/url'); 
const { data: postData, loading: postLoading, error: postError, post } = useFetch('/url', 'POST');
<DisplayData data={fetchData} />
<Button onClick={()=> post({id:4, name: 'jody'})} />

useFetch.js:

const useFetch = (url, method) => {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);
    if (!url || url.length === 0) {
        console.error('Missing url');
        if (!error) setError(true);
        return { error };
    }
    const runFetchProcess = async (data) => {
        const requestMethod = method === 'POST' ? 'POST' : 'GET';
        let requestHeader = { headers: { 'Content-Type': 'application/json' }, method: requestMethod };
        if (data) {
            requestHeader.body = JSON.stringify(data);
        }
        try {
            const response = await fetch(url, requestHeader);
            const responseData = await response?.json();
            if (!response.ok) {
                setLoading(false);
                setError(true);
                throw Error('something wrong here mate');
            } else {
                setData(responseData);
                setLoading(false);
            }
        } catch (e) {
            setError(true);
            console.error('useFetch.js', e);
        } finally {
            setLoading(false);
        }
    };
    const post = (data) => {
        if (!data) return;
        runFetchProcess(data);
    };
    useEffect(() => {
        if (method) return;
        runFetchProcess();
    }, []);
    return { data, loading, error, post };
};
export default useFetch;

Please have a look my code. Looking forward to have some great feedback.

\$\endgroup\$
2
  • \$\begingroup\$ I don't see anything obviously wrong at first glance. . Is the end user unable to navigate away from this screen during GET and POST? Be sure you are not updating state once the control is unmounted. (I can show you how if you ask) \$\endgroup\$ Apr 5 at 15:31
  • \$\begingroup\$ Be sure you are not updating state once the control is unmounted. Not sure what you mean by that ? Can you please explain bit more ? thanks ! :🙏 \$\endgroup\$
    – user257169
    Apr 6 at 6:11

1 Answer 1

0
\$\begingroup\$

Be sure to use a linter when using hooks. The linter discovered 3 issues:

  • React Hook useEffect has missing dependencies: 'method' and 'runFetchProcess'. Either include them or remove the dependency array. (react-hooks/exhaustive-deps)
  • The 'runFetchProcess' function makes the dependencies of useEffect Hook (at line 43) change on every render. To fix this, wrap the definition of 'runFetchProcess' in its own useCallback() Hook. (react-hooks/exhaustive-deps)
  • React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? (react-hooks/rules-of-hooks) -- Fixed by moving if from line 10ish to line 22ish.

I've also removed the return from use effect. The return from use effect should be a callback to cleanup code.

const useFetch = (url, method) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  if (!url || url.length === 0) {
      console.error('Missing url');
      if (!error) setError(true);
      // return { error };  // moved to line 22ish
  }
  const runFetchProcess = useCallback(async (data) => {
      const requestMethod = method === 'POST' ? 'POST' : 'GET';
      let requestHeader = { headers: { 'Content-Type': 'application/json' }, method: requestMethod };
      if(error) {
        return;
      }
      if (data) {
          requestHeader.body = JSON.stringify(data);
      }
      try {
          const response = await fetch(url, requestHeader);
          const responseData = await response?.json();
          if (!response.ok) {
              setLoading(false);
              setError(true);
              throw Error('something wrong here mate');
          } else {
              setData(responseData);
              setLoading(false);
          }
      } catch (e) {
          setError(true);
          console.error('useFetch.js', e);
      } finally {
          setLoading(false);
      }
  }, [error, method, url]);
  const post = (data) => {
      if (!data) return;
      runFetchProcess(data);
  };
  useEffect(() => {
      if (!method) {
       runFetchProcess();
      }
  }, [method, runFetchProcess]);
  return { data, loading, error, post };
};
export default useFetch;

Also, be sure you don't update state on an unmounted component. See useSafeSetState

Finally, consider simplifying / untwisting your code by creating 2 hooks, useFetch for GET and useSend for POST. Calling useFetch for posting is confusing. Also, the second parameter my be hard to remember and is error prone. Is it POST or post?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.