3

I'm a bit baffled here. According to this SO question and also this question, I should be able to call the useEffect hook after calling the useQuery hook to populate state, which I am doing via useReducer. E.g., something like this (assume that foo, bar, and baz are named queries within a single request to the GraphQL server):

const MyComponent = ( props ) => {

  const [ state, dispatch ] = useReducer( reducer, initialState )

  const query = someCondition ? query1 : query2
  const variables = { variables: someCondition ? query1Vars : query2Vars }

  const { loading, error, data } = useQuery( query, variables )

  useEffect(function() {
    dispatch({
      type: types.INIT,
      payload: {
        foo: data.foo,
        bar: data.bar,
        baz: data.baz
      }
    })
  }, [data])

  return (
    <div>
    {
      (() => {
         if ( loading ) return <Loading />
         if ( error ) return <Error />
         return (
           // components that depend on data
         )
      })()
    }
    </div>
  )
}

For reasons I can't determine, data is undefined when I reference it inside useEffect. I have checked my network responses and the call to my GraphQL endpoint is indeed returning data. (Essentially, what's described in this other SO question about useQuery.)

Except for the conditional query/variable assignment, this code is in use for some of our other components and it works fine, so I have no idea why it's not working in my case.

I'm not sure what's going on here, as I've made sure that no hooks are being called conditionally, in violation of the rules of hooks; all I'm doing is conditionally assigning variable values before sending the query via Apollo client. (I also tried the onCompleted property of the useQuery options, without the useEffect hook, but to no avail; data is still undefined.)

6
  • This looks like expected behaviour to me? useEffect will always fire before useQuery has completed, what I would expect is that useEffect would trigger twice, once when the component mounts and then secondly when useQuery returns a result. Seems to me that you aren't accounting for the initial useEffect call when data has yet to be set. Commented Jan 31, 2020 at 17:28
  • 1
    Hmm, adding a check for if ( data ) { ... } doesn't seem to account for the initial firing of the useEffect hook. github.com/trojanowski/react-apollo-hooks/issues/158 seems to suggest you don't need to use useEffect at all, but I've seen that code working previously. How should the initial render be handled? Commented Jan 31, 2020 at 17:43
  • 1
    correct, you don't because useQuery already stores the state for you - however I've no idea of your use case, only going off the code you provided where you want to get it into some form of reducer. Commented Jan 31, 2020 at 17:49
  • data will be initially undefined as @James pointed out. It can also be undefined indefinitely if network errors are encountered. That said, there's little reason to ever do this -- useQuery already manages state for you. If you need to further transform this state, then you just do so as regular variables inside your component. Commented Jan 31, 2020 at 19:06
  • Thanks @DanielRearden, as I mention in my comment below, I changed some rendering with my child components, which were apparently rendering even when the state wasn't there to support them. I did use the onCompleted callback for useQuery, but I see so many examples of useQuery + useEffect that I was certain it should work as described, or least not be actively problematic. :) Commented Jan 31, 2020 at 20:18

1 Answer 1

3

Unless I'm not fully understanding your issue, this looks like expected behaviour.

useEffect will fire on mount and then anytime data changes, data will only change when the API call has completed. The initial useEffect call is going to happen before useQuery has gotten a response from the server therefore you can't assume inside useEffect that data will be set.

If you only want to dispatch when data is there then you should protect the call e.g.

useEffect(() => {
  if (data) {
    dispatch({
      type: types.INIT,
      payload: {
        foo: data.foo,
        bar: data.bar,
        baz: data.baz
      }
    });
  }
}, [data]);
Sign up to request clarification or add additional context in comments.

5 Comments

Yes, I'd tried that check but data still seems to be undefined (the actual error just shows up a little further downstream).
@diekunstderfuge how many times does useEffect fire? Is it twice as expected? It would suggest the issue lies in useQuery then and not useEffect tbh
It looks as if useEffect is only firing once, which according to the docs should only happen if an empty array is passed as the second parameter.
I went back to the onCompleted option of useQuery and did my dispatch calls there, being sure to check for the piece of data that I needed. According to Apollo, the onCompleted callback should only execute on successful completion of the query (I defined an onError callback just in case). It works, now that I've changed some criteria for rendering child components. I'll mark this answer as accepted and, if I have time, see if it works with the modifications I made to my rendering. Thanks!
@diekunstderfuge "according to the docs should only happen if an empty array is passed" - this isn't the only scenario where this would happen, the second parameter to useEffect is a list of trigger dependencies, in other words, if any items in the array change at any point then useEffect will re-trigger - an empty array is just an easy way of making sure useEffect only ever fires once (an empty array will never change). In this example, we are seeing that useQuery doesn't appear to force a state change therefore as far as useEffect is concerned, data hasn't changed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.