React-apollo: Skip & data.refetch

Created on 24 Oct 2016  Â·  29Comments  Â·  Source: apollographql/react-apollo

Skipping a query will not allow to refetch data, since the refetch function will be undefined. Is this a bug or by design?

@graphql(gql`
  query verify($auth: String!) {
    user: verify(token: $auth) {
      user {  id, name }
    }
  }
`, {
  props: ({ ownProps, data }) => {
    console.log(data.refetch); // undefined if skipped
    return data;
  },
  options: () => ({
    skip: !localStorage.getItem('auth'),
    variables: { auth: localStorage.getItem('auth') },
  }),
})

Most helpful comment

Hello !

It seems you can achieve this using directive @skip

export default graphql(gql`
    query SearchQuery($query: String, $first: Int, $skip: Boolean!) {
        search(query: $query, first: $first) @skip(if: $skip) {
            edges {
                ....
            }
        }
    }
`,  { options: {variables: { skip: true } }})(Input);

Initial query wont be triggered, but refetch will be available :)

All 29 comments

I agree this could be made more helpful. I may be missing something in the react apollo docs, so I've opened a SO question: http://stackoverflow.com/questions/40296800/how-do-you-dynamically-control-react-apollo-client-query-initiation

Ideally, it'd be great to be able to dynamically control when a query is initiated.

Apparently skip is not evaluated in response to prop updates.

For example, I have a map which loads different types of data. I'd like to only load one type on component mount.

At the moment, if I wrap the map component in multiple apollo containers, all data types are loaded on mount or prop change.

It would be handy to have a way to prevent the initial query for each wrapping container from being fired - but to be able to trigger it later, say in response to user input.

@bkniffler, @heysailor it currently is by design because the idea is you can't refetch something not fetched? I can see the usefulness in supporting it though! Would you be able to open a PR?

It would be also awesome if we can pass some option, for example onlyInitialFetch or disableFetchingOnVariableChange so we can create component which will not listen to variable changes. This way we can make big fetch with graphql HOC and then define custom fetching on props change with fetchMore

That's a great idea. Two new would handle it:

disableAutoQuery in the query options to prevent initial and subsequent queries in response to mount and prop/variable changes

fetch as opposed to refetch - supplied as a prop to the wrapped component only if disableAutoQuery is true. Which is really only renaming the usual fetch prop.

If you want that level of control over when the query runs etc, I wonder if you should just use withApollo and run the query yourself?

Yes, that's what I'm doing currently. But it seems like a handy feature that would allow more use of the container style.

On Wed, Nov 2, 2016 at 10:01 AM +1100, "Tom Coleman" [email protected] wrote:

If you want that level of control over when the query runs etc, I wonder if you should just use withApollo and run the query yourself?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@tmeasday It is fine to use withApollo but you have to do quite a lot of logic myself and don't know if it will be cleaner
@heysailor can you create some snippet on gist with your usage?

Hey Michel, I'm not doing anything fancy but doing a direct call to 'apolloClient.query({ options })' - documented in the API reference of the Apollo client. It returns a promise with a data parameter which I parse in the same way you can parse the Apollo result from a containerised call.
In order to avoid data duplication and my fetched data being out of date, I pull out only the IDs of the data I need and keep them as an array. Individual child items in Apollo containers then get the data they need. Sounds awful, but with batching and client caching, there are minimal server requests.
I'd still rather be able to simplify it by passing data directly from an Apollo container parent though!

Note that with the release of Apollo client version 0.5, the query merging style of query batching is being discontinued. You need a batching capable server to do it. It would therefore now be better to avoid many individual components making queries as I was using earlier.

@heysailor We're still planning on publishing query merging in a separate package. If making lots of individual components with small queries made your code easier to understand and maintain, I would stick to that, and not make things more complicated just because there's currently no package that does batching for you if the server doesn't support it.

@helfer Any word on the concept of query merging?

there is an other issue with skip.
When reseting the store https://github.com/apollostack/apollo-client/blob/master/src/core/QueryManager.ts#L633 will actually run all queries without checking 'skip' result first.

Actually when skip change to false, graphql HoC unsubscribes from the queryObservable (https://github.com/apollostack/react-apollo/blob/master/src/graphql.tsx#L286). So I see 2 options:

  • QueryManager should not run refetch on queries without subscribers on resetStore
  • graphql HoC unsubscribeFromQuery method should actually tearDown the queryObservable instead of just unsubscribe from it (https://github.com/apollostack/react-apollo/blob/master/src/graphql.tsx#L398)

What do you think?

I did proposition 2 pull requests : https://github.com/apollostack/react-apollo/pull/342

is this still an issue? how can you disable the first automatic fetch while being able to fetch it later on? skip doesn't work

We've been discussing a bit on this topic here: https://github.com/apollographql/react-apollo/issues/196

The best way currently to skip the query while allowing to refetch is to use fetchPolicy

@graphql(
  gql`
    query xyz {
      xyz {
        id
      }
    }
  `,
  {
    options: ({ skip }) => ({
      fetchPolicy: skip? 'cache-only' : undefined,
    }),
  },
)

Basically this will force to run graphql agains the cache, for example if you got a detail view that gets an id prop, which may be empty if you want to create a new object

@graphql(
  gql`
    query xyz($id: String) {
      xyz(id: $id) {
        id
      }
    }
  `,
  {
    options: ({ id }) => ({
      fetchPolicy: !id? 'cache-only' : undefined,
      variables: { id }
    }),
  },
)

@bkniffler still don't see how this helps for my situation. If you pass an id to the component but don't want it loaded until refetch is called, this wont work, since the id is there. My use case is that I don't want to generate a signed download URL from google cloud storage unless a user clicks the download button. Can't find an easy way to only query after a user clicks :/ unless I do some weird hacky thing.

Can't you do something like:

<Download _id={id} />

@graphql(
  gql`
    query xyz($id: String) {
      xyz(id: $id) {
        id
      }
    }
  `,
  {
    options: ({ id }) => ({
      fetchPolicy: !id? 'cache-only' : undefined,
      variables: { id }
    }),
  },
)
class Download extends Component {
  triggerRefetch(){
    refetch({ id: this.props._id })
  }
}

I agree its hacky and I'd wish for some proper way to control skipping.

@bkniffler oh okay, I thought options only took in the component props, not the refetch variables.

Urgh, I think you're right. I think you're better off using the vanilla apollo client that you get with @withApollo. By the way, why aren't you using mutations?

@bkniffler I'm not changing anything. Just loading data, why would it be a mutation?

Hello !

It seems you can achieve this using directive @skip

export default graphql(gql`
    query SearchQuery($query: String, $first: Int, $skip: Boolean!) {
        search(query: $query, first: $first) @skip(if: $skip) {
            edges {
                ....
            }
        }
    }
`,  { options: {variables: { skip: true } }})(Input);

Initial query wont be triggered, but refetch will be available :)

I encountered a similar situation today. I'm still new to the GraphQL scene, but in my mind, I keep wondering how is this _not_ the default case for every query. Isn't most data requested _after_ some kind of async user interaction? Clicking a link, a button, filling an input.

A basic search/filter scenario is the simplest use case I can think of where this is most evident. Think about a set of results being displayed _after_ submitting a search. Is there any "proper" approach to handling this type of situations, thinking in GraphQL?

@dstendardi That is a great example! We should add it to the docs on how to achieve this! I think we can also support a variant / policy towards skipping that may make this easier as well? I'm torn because everything (that I have seen) is possible with today's skip process.

@nfantone I think GraphQL is used both in response to inputs and as a way to fetch data for the app to even render. So it is needed to be able to be called on demand and load initially

I think GraphQL is used both in response to inputs and as a way to fetch data for the app to even render.

@jbaxleyiii Well... that's not entirely true. At least, not always. Sometimes you find yourself wanting to just display components that would allow the user to provide some input that may, potentially, trigger some data fetching. Or not. So the rendering capabilities of GraphQL don't even play a part until the user interacts with the UI.

@nfantone I agree! That's what I meant by I think GraphQL is used both in response to inputs

@dstendardi Is it possible to send a different set of variables to query using your technique? From what I can tell, I'm stuck with the variables that are hard coded in it when you do the composition.

@dstendardi ´s solution works fine for the use case of skipping initially and calling the query on button press!

This doesn't work if you don't want to fire an initial query to your GraphQL endpoint. I don't really see this as a viable solution.

For people still wondering the best way to load a <Query> on demand, here is a <DeferredQuery> component that seems to work ok: https://gist.github.com/jaydenseric/5ff7ebeafca16da32a9fdb9055e99e1e

created a custom hook with useState as a workarround.
In my case onCompleted was fired, however data from useQuery was not updated

const useTrackingData = (id) => {
  const [trackingData, setTrackingData] = useState();
  const { refetch } = useQuery(
    QUERY,
    {
      variables: { param: id },
      skip: !id,
      onCompleted: (data) => {
        if(data) setTrackingData(data)
      }
    },
  );

  const fetchTracking = async (param) => {
    const {data} = await refetch(param)
    setTrackingData(data)
  }

  return [trackingData, fetchTracking]
}
Was this page helpful?
0 / 5 - 0 ratings