skip to Main Content

I have a custom hook that return an object by parameter 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. Any idea how to implement it?

The custom hook:

useBuilder.tsx

export default function useBuilder(user) {
  const api = useGlobalApi()
  const user = ...

 // doing a lot of requests and logic here that depends on the current user

  ...

  return builder
}

For each selected user I need a new builder object constructed and returned from the custom hook depends on the selected user.

The usage:

Api.tsx

export default function User() {
  const [users, setUsers] = useState([])
  const [user, setUser] = useState({})
  const [builder, setBuilder] = useState({})

  const usersData = api.get('/../users')

  useEffect(() => {
    if (!usersData?.length) return
    setUsers(usersData)
    setUser(usersData[0])
  }, [usersData])

  useEffect(() => {
    const builder = useBuilder(user)
    setBuilder(builder)
  }, [user])

  return (
    <Select
      value={user}
      onChange={e => {
        setUser(user)
      }}
    >
      {users.map(user => (
        <MenuItem key={user.id} value={user}>
          {user.name}
        </MenuItem>
      ))}
    </Select>
  )
}

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`

Any idea how can I resolve this and get the expected result?

Edit:

I want to avoid touching the custom hook since this is a global custom hook with a lot of usages.

3

Answers


  1. You need to write a function component that returns the custom hook. The use… prefix only obeys the hook rules when in a function.
    Read the docs:react docs

    Login or Signup to reply.
  2. Remove the effect where you call useBuilder, just call the custom hook at the top level of your component. However, the custom hook will be called when either the users or user state variable changes, but you should still get the result that you want since useBuilder does its logic based only on the user.

    export default function User() {
      const [users, setUsers] = useState([])
      const [user, setUser] = useState({})
      // const [builder, setBuilder] = useState({}) // remove this
      const builder = useBuilder(user) // use custom hook here
    
      const usersData = api.get('/../users')
    
      useEffect(() => {
        if (!usersData?.length) return
        setUsers(usersData)
        setUser(usersData[0])
      }, [usersData])
    
      return (
        <Select
          value={user}
          onChange={e => {
            setUser(user)
          }}
        >
          {users.map(user => (
            <MenuItem key={user.id} value={user}>
              {user.name}
            </MenuItem>
          ))}
        </Select>
      )
    }
    
    Login or Signup to reply.
  3. export default function customBuilder(){
      function useBuilder(user) {
      const api = useGlobalApi()
      const user = ...
    
     // doing a lot of requests and logic here that depends on the current user
    
      ...
    
      return builder
    }
    
    return useBuilder;
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search