react-relay-offline icon indicating copy to clipboard operation
react-relay-offline copied to clipboard

fetchQuery results not being added to store cache

Open matt-dalton opened this issue 6 years ago • 14 comments

It's quite possible this is because of another library I'm using (or I've set something up wrong), but would at least like to check my usage appears OK...

I'm finding caching and Async storage persistance is generally working great 👍 If I print out environment._store._cache.data I can see the queries I have visited successfully being added. The one exception to this is a query that I call imperatively using fetchQuery, which is causing me problems.

I create my Network/Environment like this:

import {
    RelayNetworkLayer,
    urlMiddleware,
    authMiddleware,
    errorMiddleware,
    loggerMiddleware,
    retryMiddleware,
    batchMiddleware
} from 'react-relay-network-modern'

const config = { noThrow: true }

const network = new RelayNetworkLayer(
    [
        urlMiddleware({
            url: () => Promise.resolve(`${API_BASE_URI}/graphql/`)
        }),
        batchMiddleware({
            batchUrl: () => Promise.resolve(`${API_BASE_URI}/graphql/`),
            batchTimeout: 80
        }),
        authMiddleware({
            token: AsyncStorageController.getAuthToken,
            prefix: 'Token '
        }),
        retryMiddleware({
            fetchTimeout: 10 * 1000, // 10 seconds
            retryDelays: attempt => Math.pow(2, attempt + 4) * 100
        }),
        loggerMiddleware(),
        errorMiddleware()
    ],
    config
)

const persistStoreOptions = { defaultTTL: 1000 * 60 * 60 }

const recordSource = new RecordSource(persistOptions)

const store = new Store(recordSource, persistStoreOptions)

const env = new Environment(
            {
                network,
                store
            },
            {}
        )

I am then using fetchQuery like this:

import { fetchQuery, graphql } from 'react-relay-offline'
export const query = graphql`
    query appStartQuery {
        viewer {
            me {
                id
            }
       }
    }
`
const data = await fetchQuery(relayEnv.environment, query)

Is there any setup here for react-relay-offline I've missed? Is there anything I could debug here that might point me in the right direction?

matt-dalton avatar Oct 21 '19 18:10 matt-dalton

fetchQuery does not insert the record in the store but only in the recordsource. To insert a record in the Store it is necessary to manage the retain function of the environment. This is the same behavior as react-relay.

morrys avatar Oct 21 '19 18:10 morrys

Ahh gotcha. Does that mean there is no way to default to loading these values from the cache? And you can only use withQueryRenderer results offline?

matt-dalton avatar Oct 21 '19 19:10 matt-dalton

fetchQuery data is deleted after the garbage collector is run ...

@sibelius can you indicate where the retain function is documented and how it should be used in combination with the fetchQuery?

morrys avatar Oct 21 '19 19:10 morrys

Interesting. I've been playing about with this, and see that you can write something like

import { createOperationDescriptor, getRequest } from 'relay-runtime'

const operation = createOperationDescriptor(getRequest(query))
data = await fetchQuery(relayEnv.environment, query)
relayEnv.environment.retain(operation.root)

and then I see data is retained in the store.

Problem is, I then want to be able to fallback to the cache data if I'm offline, which is what the react-relay-offline QueryRenderer does nicely. Is there a way using the library of replicating this logic?

I tried using relayEnv.environment.lookup(operation.fragment, operation).data but this seems to return undefined

matt-dalton avatar Oct 22 '19 15:10 matt-dalton

I can't understand your use case, it looks like you're completely recreating the queryrenderer

morrys avatar Oct 22 '19 15:10 morrys

I'd like to be able to do exactly that - recreate the use-cache-if-offline functionality from the query renderer but using the imperative fetchQuery. Is there a reason why this is a bad idea?

matt-dalton avatar Oct 22 '19 16:10 matt-dalton

Not necessarily a bad idea, I'm curious about how you're using the library / offline to see if there's anything to improve in react-relay-offline or recommend other solutions.

relayEnv.environment.lookup (operation.fragment, operation) .data should be the correct way to retrieve information from the store. If it returns the value null it means that not all the data necessary to the query are present.

morrys avatar Oct 22 '19 16:10 morrys

You seem to be dealing with the same theme as my example project: https://github.com/morrys/offline-examples/blob/master/relay/nextjs/relay/withData.tsx

morrys avatar Oct 22 '19 16:10 morrys

This use case is possible with new relay hooks on experimental package

sibelius avatar Oct 22 '19 17:10 sibelius

I'm curious about how you're using the library / offline to see if there's anything to improve in react-relay-offline or recommend other solutions.

Our use case: We have one app query that we run imperatively using fetchQuery. The rest of the app now works nicely offline, and would be nice to have the same offline logic here (i.e. if offline get from cache)

As a new user to this library I would have expected the lib's fetchQuery to work the same way as QueryRenderer in this sense. But now the different behaviour seems like more of a Relay quirk, so can see why you haven't implemented it.

This use case is possible with new relay hooks on experimental package

Cool thanks. What specifically will this be addressing @sibelius - adding imperative requests to the cache by default?

matt-dalton avatar Oct 22 '19 19:10 matt-dalton

I imagine that fetchQuery is not supposed to manage the Store as it is a direct request on the network.

What seems strange to me is that the lookup gives null.

For your use case the correct procedure is as follows:

online: fetchQuery, retain unmount component: dispose retain offline: lookup in the store

Obviously the lookup returns null when the garbage collector has eliminated the references in the store.

During the offline state the garbage collector is disabled.

morrys avatar Oct 23 '19 09:10 morrys

OK cool - well good to know I was on the right lines. Perhaps there's something I've done wrong - will keep looking. Thanks!

matt-dalton avatar Oct 23 '19 10:10 matt-dalton

I imagine that fetchQuery is not supposed to manage the Store as it is a direct request on the network.

What seems strange to me is that the lookup gives null.

For your use case the correct procedure is as follows:

online: fetchQuery, retain unmount component: dispose retain offline: lookup in the store

Obviously the lookup returns null when the garbage collector has eliminated the references in the store.

During the offline state the garbage collector is disabled.

Hi morrys, I've also been following this issue, and I was wondering that wouldn't the unmount dispose retain stop the values from being persisted offline, as we're clearing the store? Could you please expand on this a little more?

Thanks!

octodhruv avatar Nov 06 '19 16:11 octodhruv

hi @octodhruv, I try to answer but maybe I didn't understand your question well.

It is necessary to make the dispose to allow the garbage collector to clean the store and avoid growing keeping invalid data.

In react-relay-offline I added the TTL in the queries and a default value for all the queries that are executed (10 minutes). This means that even if a query has been disposed, the garbage collector does not delete it until the TTL expires.

Furthermore, when the application is offline I disabled the garbage collector.

morrys avatar Nov 06 '19 18:11 morrys