trojanowski / react-apollo-hooks

Use Apollo Client as React hooks
MIT License
2.41k stars 109 forks source link

How do I use onComplete with useQuery ? #106

Open bluedusk opened 5 years ago

bluedusk commented 5 years ago

Hi guys,

I want to do something after each polling but couldn't find how to use onCompleted:

onCompleted: (data: TData | {}) => void A callback executed once your query successfully completes.

Any idea how to use this with useQuery ? Thanks !

janhesters commented 5 years ago

I'm facing the same issue. I'm using this with AWS Amplify.

Before my code looked like this:

  async function fetchContacts() {
    try {
      const contactData = await API.graphql(
        graphqlOperation(queries.listContacts)
      );
      setContacts(
        replaceNillInArrayWithEmptyString(contactData.data.listContacts.items)
      );
    } catch (err) {
      console.log('error: ', err);
    }
  }

  useEffect(() => {
    fetchContacts();
  }, []);

Now, I would need . something like onCompleted. I'm thinking about useEffect maybe? Like this:

const { data, error, loading } = useQuery(queries.listContacts);

useEffect(() => {
  if(!error && !loading) {
    setContacts(
      replaceNillInArrayWithEmptyString(data.listContacts.items)
    );
  }
}, [data, error, loading])

Is this okay?

leoc commented 5 years ago

I just stumbled over this too. @janhesters, your useEffect looks good.

The react-apollo Query component does it like this:

https://github.com/apollographql/react-apollo/blob/5cb63b3625ce5e4a3d3e4ba132eaec2a38ef5d90/src/Query.tsx#L228-L239

Following the same logic:

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

  useEffect(() => {
    const onCompleted = (data) => { /* magic */ };
    const onError = (error) => { /* magic */ };
    if (onCompleted || onError) {
      if (onCompleted && !loading && !error) {
        onCompleted(data);
      } else if (onError && !loading && error) {
        onError(error);
      }
    }
  }, [loading, data, error]);

If react-apollo-hooks included this I think there should either be a call to the callback to the specified in the props or a useEffect after the useMemo that does this:

https://github.com/trojanowski/react-apollo-hooks/blob/master/src/useQuery.ts#L134-L154

pak11273 commented 5 years ago

@leoc If I add useState logic in the onCompleted function I get caught in a loop. I can get fix this by adding state to the dependency array but then I get a warning about missing dependency: state. If I put the state in the dependency I get the loop again. Is there a work around for this or can we not use useState in the onCompleted function?

leoc commented 5 years ago

@pak11273 Could you post example code? Generally you can only use hooks (e.g. useState) within functional react components. So you should not put useState into callbacks. What you could do is calling set<State> from within callbacks though.

pak11273 commented 5 years ago

This is the functional react component I'm using. I'm also using react-apollo-hooks but I don't think that is affecting anything.

 const CourseUpdate = () => {
   const [state, changeState] = useState({
     data: []
   })

   const {data, error, loading} = useQuery(getBooks, {
     variables: {
       courseId: course._id
     }
   })

   useEffect(
     () => {
       const onCompleted = data => {
         changeState({
           ...state,
           data: data.getBooks.books
         })
       }
       const onError = error => {
         return (
           <div>{error}</div>
         )
       }
       if (onCompleted || onError) {
         if (onCompleted && !loading && !error) {
           onCompleted(data)
         } else if (onError && !loading && error) {
           onError(error)
         }
       }
     },
     [loading, data, error]
   )

   if (loading) {
     return <div>...loading</div>
   }
}
fbartho commented 5 years ago

I haven't worked much with useEffect yet, but I think you want to push the useQuery inside the useEffect callback.

Assuming that you want useEffect at all. -- Are you sure you need useEffect?

You could instead simply load the data and render it. (Remove the useEffect entirely!)

pak11273 commented 5 years ago

@fbartho I need useEffect to mimic componentDidMount. The useQuery function is basically another react hook, so it can't be used inside of a callback, it needs to be inside of a react functional component.

vmptk commented 5 years ago

@pak11273, Does the useQuery triggered twice in the snippet? I have similar implementation and observe at least double execution of the query.

`const ClientRiskSummaryPanel = () => { const {dispatch} = useContext(Context); const {data, error, loading} = useQuery(QUERY);

useEffect(() => {
    const onCompleted = data => {
        dispatch({type: ADD_MESSAGE, payload: data.getMessage});
    };
    const onError = error => {
        return <Error message={error} />;
    };
    if (onCompleted || onError) {
        if (onCompleted && !loading && !error) {
            onCompleted(data);
        } else if (onError && !loading && error) {
            onError(error);
        }
    }
}, [loading, data, error]);

if (loading) {
    return <div>Loading...</div>;
}

return (
    <List component="nav">

... `

pak11273 commented 5 years ago

@vmptk No. It only fires once. The code I have works, it just bugs me that I have to silence an eslint warning.

leoc commented 5 years ago

Normally I put [all, my, query, variables] into the useQuery to avoid multiple runs when other state is changing.

@pak11273 I don't understand why you would need another state (useState). Using the data from useQuery should suffice, no?

I needed the onComplete to trigger another query which needed data from the first query.

pak11273 commented 5 years ago

@leoc Maybe I can just use the data from useQuery, I'll try that. I was putting data into useState because that data is a list of books which will be manipulated by users. I generally put data from api calls into state. I don't know if that's a good practice or not.

cziem commented 5 years ago

Can you use useQuery() from within the useEffect() hook?

leoc commented 5 years ago

Can you use useQuery() from within the useEffect() hook?

Hi @phavor, no you can't, since react-hooks cannot be used in the callbacks of react-hooks. I suggest you read up on using react-hooks with functional components. This helps a lot. The concept might be confusing otherwise :)

leoc commented 5 years ago

@leoc Maybe I can just use the data from useQuery, I'll try that. I was putting data into useState because that data is a list of books which will be manipulated by users. I generally put data from api calls into state. I don't know if that's a good practice or not.

The useQuery composed hook should actually use a useState inside for the data. So you can just use the state from the useQuery return value.

ncux199rus commented 3 years ago

it works for me like this:

const [token, setToken] = useState()

  useQuery(queryGetTocken, {
    onCompleted: (data) => {
      setToken(data.auth.access)
    },
  })
mdeividas commented 1 year ago

it works for me like this:

const [token, setToken] = useState()

useQuery(queryGetTocken, {
  onCompleted: (data) => {
    setToken(data.auth.access)
  },
})

Probably this is already outdated, but what I did find is that you can use onSettled instead of onCompleted

this is from the docs:

/**
* This callback will fire any time the query is either successfully fetched or errors and be passed either the data or error.
*/
onSettled?: (data: TData | undefined, error: TError | null) => void;
hickscorp commented 7 months ago

I'm also failing what's the idiomatic way to use state management with useQuery in conjunction to most React idiomatic effect management hooks.

I've been told recently that using useEffect on the data of a useQuery result is a bad idea - but havn't been told "why" to make myself my own opinion... I tried to implement the same thing using exclusivelly useQuery's onCompleted callback - but it doesn't get called when re-triggering the query from another query (Eg using the refetchQueries options).

Being fluent in React, I find it frustrating that the documentation is a bit all over the place, and there's no single FAQ on good practices for the React Apollo hooks. Anyone please?