8

I know that one of the fixed rules in React is that you cannot execute any hooks inside methods in a functional component or even inside other hooks like the useEffect hook. It must be executed in the body of the component itself.

So today while working on a quiz app, I ran into a situation where, after passing a screen on the stack navigator, I want to go back to that screen again by clicking on the back button. But this time, I want to modify the info on the previous screen with data from a graphql query using the useQuery hook provided by Apollo Client.

I can detect if the previous screen is focus using the isFocused property on the props. If it changes, then I will fetch data again using useQuery.

I can do this simply like so:

    React.useEffect(() => {
        //how do I fetch data from here using useQuery if React prohibits me to use useQuery here?
    }, [props.isFocused]);

So that is my question, is there a way around this? I'll appreciate your response.

Awa Melvine
  • 3,797
  • 8
  • 34
  • 47

5 Answers5

4

Found this question with a similar problem on reactjs, where I loaded a page with data then went to another page to add a document to the database then on completion goes back to the original page.

Changing the default fetchPolicy to network-only ensure I was always getting the latest data available.

const { loading: loadingData, error, data } = useQuery(GetData, {
    fetchPolicy: "network-only"

reference: https://www.apollographql.com/docs/react/data/queries/#setting-a-fetch-policy

imGreg
  • 900
  • 9
  • 24
  • Thanks this worked a charm. I read the page in the docs you suggested and decided to use fetchPolicy: "cache-and-network" because that worked just as well and it apparently takes data from the cache sometimes which sounds like a good thing. – dean schmid Jan 20 '21 at 05:22
4

This answer assumes that you are using react-navigation to create your stack navigator in React Native app

The useQuery hook in Apollo Client runs the query whenever the component is mounted. In 'react-navigation' when you navigate to the next screen in your stack the previous(current) screen does not unmount. So when you go back the component is already mounted and therefore useQuery does not run.

To tackle this, we have to listen to the lifecycle events of react navigation. Most importantly, the focus event. We can refetch the query when the focus event of react navigation is called.

const { loading, data, refetch } = useQuery(QUERY);

React.useEffect(() => {
  const unsubscribe = navigation.addListener('focus', () => {
    // This check is to prevent error on component mount. The refetch function is defined only after the query is run once
    // It also ensures that refetch runs only when you go back and not on component mount
    if (refetch) {
      // This will re-run the query
      refetch();
    }
  });

  return unsubscribe;
  }, [navigation]);

This is how we ensure that the query gets called every time the screen is opened, in our react native apps.

Pawan Samdani
  • 1,594
  • 1
  • 10
  • 11
1

As you said, you can't call hooks inside other hooks or methods. In other words, you can't call hooks conditionally. If you use the useQuery hook, you have to be prepared that it will run the query on every re-render of the component (although, it caches the results so you shouldn't worry about the performance).

Instead, you should just call the useQuery hook inside the component and just force an update of the component on focus. It should already be re-rendering on focus, since the prop isFocused changes, presumably. If not, you can refer to this.

giotskhada
  • 2,326
  • 1
  • 11
  • 18
1

I'm struggling with this problem too and came up with this workaround with useEffect. So,

const { loading, data, refetch } = useQuery(Query_Data)


 useEffect(()=> {
      refetch()
  }, [refetch]

)

This works for history.goBack(), however, it also doubles server call when browser refreshes (one from useQuery and one from useEffect). To avoid this issue, I added a global property "global.refreshOnGoBack" which is set to true before I call history.goBack(), and then

  useEffect(()=> {
    if (global.refreshOnGoBack) {
      refetch()
      global.refreshOnGoBack = false
    }
  }, [refetch])

It seems to work fine, but I don't like it. I'm posting here more for suggestions than an answer.

newman
  • 6,841
  • 21
  • 79
  • 126
1

useQuery is a hook that ties the execution with component rendering by default. However, it's common to want this behavior be associated with some other event. In your case, it would be on screen focus. You can take control of the execution by using useLazyQuery hook instead. Think of this hook as useMutation but for queries.

From the apollo documentation:

The useLazyQuery hook is perfect for executing queries in response to events other than component rendering. This hook acts just like useQuery, with one key exception: when useLazyQuery is called, it does not immediately execute its associated query. Instead, it returns a function in its result tuple that you can call whenever you're ready to execute the query.

ron4ex
  • 1,073
  • 10
  • 21