AMR
AMR

Reputation: 146

How to wrap a function that uses hooks inside a useEffect?

I wrote a function to make an API call. Typically, I'd just wrap it in a useEffect and throw it in the same file that needs it, but I'm trying to write my code a little cleaner. So I did the following.

In my component.js file, I have the following:

import { apiCall } from '../../../framework/api.js';
import { useEffect, useState } from 'react';

export const Table = () => {
    const [ resp, setResp ] = useState();

    
    useEffect(() => {
        console.log(apiCall());
    }, []);
    
    return(
        <>
            { resp &&
                resp.map(([key, value]) => {
                    console.log("key: " + key);
                    return(
                        <SomeComponent />
                    );
                })
            }
        </>
    );
}

in my api.js file, I have the following:

import axios from 'axios';
import { useState } from 'react';

export const apiCall = () => {
    const [ resp, setResp ] = useState();

    axios.get('https://some.domain/api/get').then((response) => {
        setResp(response.data);
    });

    if(resp) return resp;
}

This always returns an error (Invalid hook call. Hook calls can only be called inside the body of a function component.)

If I rewrite my component.js and include the axios call directly inside useEffect instead of calling the function apiCall() from the external file, it obviously works with no problems.

I think I know it has to do with the fact that I'm using hooks in my apiCall function, and wrapping that call in a useEffect in my component.js. However, if I don't wrap it in a useEffect, it'll just run continuously and I don't want that either.

Upvotes: 2

Views: 4646

Answers (3)

Onkar Kole
Onkar Kole

Reputation: 1357

React Hook "useState" is called in function "apiCall" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".

You can use following methods.

import { useState } from 'react';

export const ApiCall = () => {
  const [state, setState] = useState();
};

or

import { useState } from 'react';

export const useApiCall = () => {
  const [state, setState] = useState();
};

Upvotes: 0

asinkxcoswt
asinkxcoswt

Reputation: 2554

Usually, we do it like this

export const useApiCall = () => {
    const [ resp, setResp ] = useState();

    useEffect(() => {
        axios.get('https://some.domain/api/get').then((response) => {
            setResp(response.data);
        });
    }, []);

    return resp;
}

and then use it like so

export const Table = () => {
    const resp = useApiCall();
    
    return(
        <>
            { resp &&
                resp.map(([key, value]) => {
                    console.log("key: " + key);
                    return(
                        <SomeComponent />
                    );
                })
            }
        </>
    );
}

The prefix "use" in the function name is important, this is how we define a custom hook.

Upvotes: 1

Jevon Cochran
Jevon Cochran

Reputation: 1774

You have to follow the custom hook naming convention for this to be able to work. You can check out the documentation for that here: https://reactjs.org/docs/hooks-custom.html

Anyway, I believe in this case this should work:

import axios from 'axios';
import { useState } from 'react';

export const useApiCall = () => {
    const [ resp, setResp ] = useState();

    axios.get('https://some.domain/api/get').then((response) => {
        setResp(response.data);
    });

    if(resp) return resp;
}

And then in component.js, you would call useApiCall()

Upvotes: 3

Related Questions