morrys / react-relay-offline

TypeScript library files for Relay Modern Offline
https://morrys.github.io/react-relay-offline/docs/react-relay-offline.html
MIT License
227 stars 15 forks source link

fetchQuery results not being added to store cache #29

Open matt-dalton opened 4 years ago

matt-dalton commented 4 years ago

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?

morrys commented 4 years ago

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.

matt-dalton commented 4 years ago

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?

morrys commented 4 years ago

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?

matt-dalton commented 4 years ago

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

morrys commented 4 years ago

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

matt-dalton commented 4 years ago

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?

morrys commented 4 years ago

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 commented 4 years ago

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

sibelius commented 4 years ago

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

matt-dalton commented 4 years ago

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?

morrys commented 4 years ago

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.

matt-dalton commented 4 years ago

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

octodhruv commented 4 years ago

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!

morrys commented 4 years ago

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.