0

I have a custom hook that returns a value by parameters that it gets. I need to recreate this with any updated parameter so I need to do that inside useEffect but I can't call the hook inside useEffect. How could I implement it?

The custom hook:

useApi.tsx

export default function useApi(id) {
  const api = useGlobalApi()
  const user = api.get(`..../${id}`)

  ...

  return user
}

Api.tsx

export default function Api(id) {
  const [user, setUser] = useState({})
  const data = useApi(id)

  useEffect(() => {
    const user = useApi(id)
    setUser(user)
  }, [id])



  ...
}

I'm getting this error:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app`

How can I resolve this and get the expected result?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
ESI
  • 1,657
  • 8
  • 18
  • does [this Q&A](https://stackoverflow.com/questions/75594366/react-useapi-hook-results-in-endless-loop-when-used-in-useeffect/75594967#75594967) help you solve the problem? – Mulan Mar 18 '23 at 21:14
  • 1
    Why are you recursively calling `useApi`? – Unmitigated Mar 18 '23 at 21:18
  • updated, by mistake, theses are not the real components, just reflects the logic of my components that bigger and more complicated. – ESI Mar 18 '23 at 21:26

3 Answers3

0

Return a memoized function (wrapped with useCallback) from the hook, and use that function in the useEffect.

Custom hook:

export default function useApi(id) {
  const api = useGlobalApi()
  
  return () => useCallback(() => api.get(`..../${id}`), [id])
}

Usage:

const getUser = useApi(id)

useEffect(() => {
  const callApi = async () => {
    const user = await getUser()
    setUser(user)
  }

  callApi()
}, [getUser])

You can also get an immutable function, and pass the id in the useEffect:

export default function useApi() {
  const api = useGlobalApi()
  
  return () => useCallback(id => api.get(`..../${id}`), [])
}

Usage:

const getUser = useApi()

useEffect(() => {
  const callApi = async () => {
    const user = await getUser(id)
    setUser(user)
  }

  callApi()
}, [getUser, id])
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • The `useApi` is a global component, I can't touch it, any idea? Does it make sense to create a wrap component for this use case and implement there the `useCallback`? – ESI Mar 18 '23 at 21:20
  • You don't change the original `useApi`. Just your custom hook. – Ori Drori Mar 18 '23 at 21:23
  • The custom hook is global, too, and I don't want to touch it. – ESI Mar 18 '23 at 21:25
  • The dependency seems not correct in your usage, I need this to be updated by `id`, how can I recreate the response that I get from the `useApi` hook by the id (in your answer the `getUser ` should be a value not a function that I can call it), I have to send the `id` every change. – ESI Mar 18 '23 at 21:47
  • A new `getUser` function is generated every time that a new `id` is passed. – Ori Drori Mar 18 '23 at 21:51
  • Should I pass it here? `const user = await getUser()` – ESI Mar 18 '23 at 21:56
  • You pass it when you call `useApi(id) {`, and get a function with the `id` inside. When the `id` changes, the function would also change, and would trigger the `useEffect`. – Ori Drori Mar 18 '23 at 21:58
  • See 2nd option for passing `id` to the function in `useEffect`. – Ori Drori Mar 18 '23 at 22:00
  • First of all, thank you! second, I have tried it, I can't touch my custom hook - `useApi` hook so I can't insert into the `useCallback` only the api request as you suggested so I created a new wrapping custom hook that try to call the original custom hook inside a callback but I still get the same error, any idea? Thanks! – ESI Mar 18 '23 at 22:07
  • If you call a hook inside a custom hook, and then you call that custom hook inside `useEffect`, you'll get the same error message. In addition, see the new experimental `use` hook - https://vived.io/new-hook-is-coming-to-react-frontend-weekly-vol-109/. – Ori Drori Mar 18 '23 at 22:10
  • > If you call a hook inside a custom hook, and then you call that custom hook inside useEffect, you'll get the same error message. Correct, but the `useCallback` should not prevent this? this isn't the logic you suggested or if I implement it for the whole custom hook this should not work? – ESI Mar 18 '23 at 22:14
  • The solution is a function that is returned from the hook, to prevent the need of running a hook inside of `useEffect`, which is prohibited. The `useCallback` just prevents the function from being re-created unless it's deps changes. – Ori Drori Mar 18 '23 at 22:18
  • so still not sure how does it solve my case...? can you kindly clarify? – ESI Mar 18 '23 at 22:21
0
export default function useApi(id) {
    const [user,setUser]=useState(null)
    // maybe use a different name for useApi here, current hook name is `useApi` 
    // return value is usually set inside useEffect. 
    const api = useGlobalApi()

    useEffect(()=>{
  
    const userRes = api.get(`..../${id}`)
    setUser(userRes)
    },[])
    return user
  }

then inside Api.tsx

export default function Api(id) {
    const user=useApi()
    ...
  }
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • I can't insert the `useApi` component inside the `useEffect`, this is a big _global_ component that doing a lot of requests. Any idea? Thanks! – ESI Mar 18 '23 at 21:16
  • 1
    @ESI move it outside useEffect inside `useApid(di)`. you have two functions with same name – Yilmaz Mar 18 '23 at 21:18
  • By mistake, updated, theses are not the real components, just reflects the logic of my components that bigger and more complicated. – ESI Mar 18 '23 at 21:24
  • @ESI the logic behind hooks is you use useState and useEffect the way how I constructred the `useApi`. if you followed the same in `useGlobalApi`, above code should work – Yilmaz Mar 18 '23 at 21:28
  • I see that you combined the logic, you inserted the custom hook logic inside the component, I can't do that, I must to leave that separate so how I can call it every change if I can't do that in useEffect? any suggestion? Thanks! – ESI Mar 18 '23 at 21:35
  • 1
    @ESI i did not use component. it is just a hook function. setting state inside `useEffect` with correct dependency array. – Yilmaz Mar 18 '23 at 21:40
  • But it forces me to touch the custom hook, I want to avoid that – ESI Mar 18 '23 at 21:42
0

Your question is a bit confused because you are using 2 hooks called 'useApi', but I'll try my best to help.

Since you are using a hook, you don't need to call is again on useEffect.

Api.tsx

export default function Api(id) {
  const [user, setUser] = useState({})
  const usr = useApiCustom(id)

  useEffect(() => {
    setUser(usr)
  }, [usr])

  ...
}

useApiCustom.tsx

export default function useApiCustom(id) {
  const api = useApi()
  const user = api.get(`..../${id}`)

  return user
}
  • updated, by mistake, theses are not the real components, just reflects the logic of my components that bigger and more complicated. – ESI Mar 18 '23 at 23:45
  • Did try my answer? Is it working? – Rodrigo Fabbrini Mar 19 '23 at 00:40
  • By your answer I saw that my example doesn't exactly match my use case, posted a new question with more specific example, can you kindly take a look [here](https://stackoverflow.com/q/75781017/13029900)? Thanks! – ESI Mar 19 '23 at 09:12