16

I have been dealing with a few hook-related issues recently as I have been implementing hooks into a project of mine. I keep getting the error "Rendered more hooks than during the previous render."

It seems that the only way I can get my code to work is by putting the useQuery hook after all of the other hooks. This is a problem however as I want to populate some values of state with values from data on the query.

// code that doesn't error, but am not able to initialize state with query values

const [url, setUrl] = useState('')

const updateLink = useMutation(LINK_UPDATE_MUTATION, {
  variables: {
    id: props.id,
    url
  }
})

const { data, loading, error } = useQuery(LINK_QUERY, {
  variables: {
    id: props.id
  }
})

if (loading) {
  return <div>Loading...</div>
}
if (error) {
  return <div>Error! {error.message}</div>
}

vs

// code that errors with 'Rendered more hooks than during the previous render.'

const { data, loading, error } = useQuery(LINK_QUERY, {
    variables: {
      id: props.id
    }
  })

  if (loading) {
    return <div>Loading...</div>
  }
  if (error) {
    return <div>Error! {error.message}</div>
  }

const updateLink = useMutation(LINK_UPDATE_MUTATION, {
    variables: {
      id: props.id,
      url
    }
  })

const [url, setUrl] = useState(data.link.url)

I would expect that the useQuery hook could be used in a way to initialize other values with its query data.

If this isn't enough code or more explanation is needed just let me know. Thanks.

ElektrikSpark
  • 613
  • 1
  • 8
  • 21

1 Answers1

31

What you need to do is to update the state when the first hook results in a response. To do that you can make use of useEffect hook. You need to render all hooks at the top of your functional component.

const [url, setUrl] = useState('')

const updateLink = useMutation(LINK_UPDATE_MUTATION, {
  variables: {
    id: props.id,
    url
  }
})

const { data, loading, error } = useQuery(LINK_QUERY, {
  variables: {
    id: props.id
  }
})

useEffect(() => {
  if(data && data.link) {
    setUrl(data.link.url);
  }
}, [data])

if (loading) {
  return <div>Loading...</div>
}
if (error) {
  return <div>Error! {error.message}</div>
}
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • 1
    Thank you for the reply. I now get the error 'Cannot read property 'url' of undefined' after adding the useEffect hook. I believe this error indicates that the data from the database did not come back before --> `useEffect(() => { setUrl(data.link.url) }, [data.link.url])` How should I go about delaying the useEffect hook until the data comes back from the database? The `if (loading)` takes care of that, but if I move the useEffect hook below that I continue to get the same error as before. – ElektrikSpark Apr 02 '19 at 11:34
  • useEffect runs on the initial render as well. Also you must pass `data` as the key instead of `data.link.url` to useEffect dependency array and then check for data value in the effect. I updated my answer to reflect the change – Shubham Khatri Apr 02 '19 at 11:59
  • 1
    @ShubhamKhatri what happens when `data` is an array or object? since the dependency array only does shallow comparisons, that useEffect will be called as often as the component rerenders since the referenced value changes – Luminusss Dec 04 '19 at 17:14
  • 1
    @Luminusss, in such cases you need to make sure that data reference changes only while updating its state, since inthe above case useQuery won't return you a new reference everytime it runs, it would work seemlessly – Shubham Khatri Dec 05 '19 at 05:38