Giter Site home page Giter Site logo

morrys / react-relay-offline Goto Github PK

View Code? Open in Web Editor NEW
221.0 7.0 15.0 7.12 MB

TypeScript library files for Relay Modern Offline

Home Page: https://morrys.github.io/react-relay-offline/docs/react-relay-offline.html

License: MIT License

TypeScript 95.34% JavaScript 4.29% CSS 0.15% HTML 0.23%
react relay-modern offline indexeddb typescript react-native relay

react-relay-offline's Introduction

id title
react-relay-offline
Getting Started

React Relay Offline is a extension of Relay for offline capabilities

Installation React Web

Install react-relay and react-relay-offline using yarn or npm:

yarn add react-relay react-relay-offline

Installation React Native

Install react-relay and react-relay-offline using yarn or npm:

yarn add @react-native-community/netinfo react-relay react-relay-offline

You then need to do some extra configurations to run netinfo package with React Native. Please check @react-native-community/netinfo official README.md to get the full step guide.

Main Additional Features

  • automatic persistence and rehydration of the store (AsyncStorage, localStorage, IndexedDB)

  • configuration of persistence

    • custom storage

    • different key prefix (multi user)

    • serialization: JSON or none

  • fetchPolicy network-only, store-and-network, store-or-network, store-only

  • management and utilities for network detection

  • automatic use of the policy store-only when the application is offline

  • optimization in store management and addition of TTL to queries in the store

  • offline mutation management

    • backup of mutation changes

    • update and publication of the mutation changes in the store

    • persistence of mutation information performed

    • automatic execution of mutations persisted when the application returns online

    • configurability of the offline mutation execution network

    • onComplete callback of the mutation performed successfully

    • onDiscard callback of the failed mutation

Contributing

  • Give a star to the repository and share it, you will help the project and the people who will find it useful

  • Create issues, your questions are a valuable help

  • PRs are welcome, but it is always better to open the issue first so as to help me and other people evaluating it

  • Please sponsor me

Sponsors

Memorang

react-relay-offline examples

The offline-examples repository contains example projects on how to use react-relay-offline:

  • nextjs-ssr-preload: using the render-as-you-fetch pattern with loadQuery in SSR contexts
  • nextjs: using the QueryRenderer in SSR contexts
  • react-native/todo-updater: using QueryRender in an RN application
  • todo-updater: using the QueryRender
  • suspense/cra: using useLazyLoadQuery in a CRA
  • suspense/nextjs-ssr-preload: using the render-as-you-fetch pattern with loadLazyQuery in react concurrent + SSR contexts
  • suspense/nextjs-ssr: using useLazyLoadQuery in SSR contexts

To try it out!

Environment

import { Network } from "relay-runtime";
import { RecordSource, Store, Environment } from "react-relay-offline";

const network = Network.create(fetchQuery);
const recordSource = new RecordSource();
const store = new Store(recordSource);
const environment = new Environment({ network, store });

Environment with Offline Options

import { Network } from "relay-runtime";
import { RecordSource, Store, Environment } from "react-relay-offline";

const network = Network.create(fetchQuery);

const networkOffline = Network.create(fetchQueryOffline);
const manualExecution = false;

const recordSource = new RecordSource();
const store = new Store(recordSource);
const environment = new Environment({ network, store });
environment.setOfflineOptions({
  manualExecution, //optional
  network: networkOffline, //optional
  start: async mutations => {
    //optional
    console.log("start offline", mutations);
    return mutations;
  },
  finish: async (mutations, error) => {
    //optional
    console.log("finish offline", error, mutations);
  },
  onExecute: async mutation => {
    //optional
    console.log("onExecute offline", mutation);
    return mutation;
  },
  onComplete: async options => {
    //optional
    console.log("onComplete offline", options);
    return true;
  },
  onDiscard: async options => {
    //optional
    console.log("onDiscard offline", options);
    return true;
  },
  onPublish: async offlinePayload => {
    //optional
    console.log("offlinePayload", offlinePayload);
    return offlinePayload;
  }
});
  • manualExecution: if set to true, mutations in the queue are no longer performed automatically as soon as you go back online. invoke manually: environment.getStoreOffline().execute();

  • network: it is possible to configure a different network for the execution of mutations in the queue; all the information of the mutation saved in the offline store are inserted into the "metadata" field of the CacheConfig so that they can be used during communication with the server.

  • start: function that is called once the request queue has been started.

  • finish: function that is called once the request queue has been processed.

  • onExecute: function that is called before the request is sent to the network.

  • onPublish: function that is called before saving the mutation in the store

  • onComplete: function that is called once the request has been successfully completed. Only if the function returns the value true, the request is deleted from the queue.

  • onDiscard: function that is called when the request returns an error. Only if the function returns the value true, the mutation is deleted from the queue

IndexedDB

localStorage is used as the default react web persistence, while AsyncStorage is used for react-native.

To use persistence via IndexedDB:

import { Network } from "relay-runtime";
import EnvironmentIDB from "react-relay-offline/lib/runtime/EnvironmentIDB";

const network = Network.create(fetchQuery);
const environment = EnvironmentIDB.create({ network });

Environment with PersistOfflineOptions

import { Network } from "relay-runtime";
import { RecordSource, Store, Environment } from "react-relay-offline";
import { CacheOptions } from "@wora/cache-persist";

const network = Network.create(fetchQuery);

const networkOffline = Network.create(fetchQueryOffline);

const persistOfflineOptions: CacheOptions = {
  prefix: "app-user1"
};
const recordSource = new RecordSource();
const store = new Store(recordSource);
const environment = new Environment({ network, store }, persistOfflineOptions);

CacheOptions

Store with custom options

import { Store } from "react-relay-offline";
import { CacheOptions } from "@wora/cache-persist";
import { StoreOptions } from "@wora/relay-store";

const persistOptionsStore: CacheOptions = { };
const persistOptionsRecords: CacheOptions = {};
const relayStoreOptions: StoreOptions = { queryCacheExpirationTime: 10 * 60 * 1000 }; // default
const recordSource = new RecordSource(persistOptionsRecords);
const store = new Store(recordSource, persistOptionsStore, relayStoreOptions);
const environment = new Environment({ network, store });

useQuery

useQuery does not take an environment as an argument. Instead, it reads the environment set in the context; this also implies that it does not set any React context. In addition to query (first argument) and variables (second argument), useQuery accepts a third argument options.

options

fetchPolicy: determine whether it should use data cached in the Relay store and whether to send a network request. The options are:

  • store-or-network (default): Reuse data cached in the store; if the whole query is cached, skip the network request
  • store-and-network: Reuse data cached in the store; always send a network request.
  • network-only: Don't reuse data cached in the store; always send a network request. (This is the default behavior of Relay's existing QueryRenderer.)
  • store-only: Reuse data cached in the store; never send a network request.

fetchKey: [Optional] A fetchKey can be passed to force a refetch of the current query and variables when the component re-renders, even if the variables didn't change, or even if the component isn't remounted (similarly to how passing a different key to a React component will cause it to remount). If the fetchKey is different from the one used in the previous render, the current query and variables will be refetched.

networkCacheConfig: [Optional] Object containing cache config options for the network layer. Note the the network layer may contain an additional query response cache which will reuse network responses for identical queries. If you want to bypass this cache completely, pass {force: true} as the value for this option. Added the TTL property to configure a specific ttl for the query.

skip: [Optional] If skip is true, the query will be skipped entirely.

onComplete: [Optional] Function that will be called whenever the fetch request has completed

import { useQuery } from "react-relay-offline";
const networkCacheConfig = {
  ttl: 1000
}
const hooksProps = useQuery(query, variables, {
  networkCacheConfig,
  fetchPolicy,
});

useLazyLoadQuery

import { useQuery } from "react-relay-offline";
const networkCacheConfig = {
  ttl: 1000
}
const hooksProps = useLazyLoadQuery(query, variables, {
  networkCacheConfig,
  fetchPolicy,
});

useRestore & loading

the useRestore hook allows you to manage the hydratation of persistent data in memory and to initialize the environment.

It must always be used before using environement in web applications without SSR & react legacy & react-native.

Otherwise, for SSR and react concurrent applications the restore is natively managed by QueryRenderer & useQueryLazyLoad & useQuery.

const isRehydrated = useRestore(environment);
   if (!isRehydrated) {
     return <Loading />;
   }

fetchQuery_DEPRECATED

import { fetchQuery_DEPRECATED } from "react-relay-offline";

Detect Network

import { useIsConnected } from "react-relay-offline";
import { useNetInfo } from "react-relay-offline";
import { NetInfo } from "react-relay-offline";

Supports Hooks from relay-hooks

Now you can use hooks (useFragment, usePagination, useRefetch) from relay-hooks

render-as-you-fetch & usePreloadedQuery

loadQuery

  • input parameters

same as useQuery + environment

  • output parameters * next: <TOperationType extends OperationType>( environment: Environment, gqlQuery: GraphQLTaggedNode, variables?: TOperationType['variables'], options?: QueryOptions, ) => Promise<void>: fetches data. A promise returns to allow the await in case of SSR
    • dispose: () => void: cancel the subscription and dispose of the fetch
    • subscribe: (callback: (value: any) => any) => () => void: used by the usePreloadedQuery
    • getValue <TOperationType>(environment?: Environment,) => OfflineRenderProps<TOperationType> | Promise<any>: used by the usePreloadedQuery
import {graphql, loadQuery} from 'react-relay-offline';
import {environment} from ''./environment';

const query = graphql`
  query AppQuery($id: ID!) {
    user(id: $id) {
      name
    }
  }
`;

const prefetch = loadQuery();
prefetch.next(
  environment,
  query,
  {id: '4'},
  {fetchPolicy: 'store-or-network'},
);
// pass prefetch to usePreloadedQuery()

loadLazyQuery

is the same as loadQuery but must be used with suspense

render-as-you-fetch in SSR

In SSR contexts, not using the useRestore hook it is necessary to manually invoke the hydrate but without using the await.

This will allow the usePreloadedQuery hook to correctly retrieve the data from the store and once the hydration is done it will be react-relay-offline

to notify any updated data in the store.

  if (!environment.isRehydrated() && ssr) {
      environment.hydrate().then(() => {}).catch((error) => {});
  }
  prefetch.next(environment, QUERY_APP, variables, {
         fetchPolicy: NETWORK_ONLY,
  });

Requirement

  • Version >=11.0.2 of the relay-runtime library
  • When a new node is created by mutation the id must be generated in the browser to use it in the optimistic response

License

React Relay Offline is MIT licensed.

react-relay-offline's People

Contributors

akinncar avatar dependabot[bot] avatar helielson avatar ieschalier avatar morrys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-relay-offline's Issues

Retrying mutations on network failure

Hi Morries,

im currently testing the replay of mutations in case of network failure.

Pure offline/online mode works fine, however as soon as a network query fails that one is not replayed again.
The local store reflects the change, but it won't be communicated to the server.

Am I doing something wrong here, or is this intentional? I'm kinda looking for a retry strategy, the assumption is that users may have a flaky internet connection and requests my fail due to that but should be replayed later on.

Typescript commitMutation type error.

commitMutation requires RelayModernEnvironment which is not compatible with the exported Environment from react-relay-offline implementation.

You can temporarily solve this by forcing Typescript to convert the type(obviously)

Offline equivalent of createMockEnvironment

Hi morrys!

I'm trying to unit test using this kind of approach on Relay v11.0.2, relay-offline v4.0.0

I am fetching like this:

    const isRehydrated = useRestore(environment)

    const qrOptions = {
        fetchPolicy,
        ttl,
        networkCacheConfig: options.cacheConfig
    }

    const { data, error: _error, retry, isLoading: relayLoading } = useQuery(
        options.query,
        variables,
        qrOptions
    )

    if (!isRehydrated || !data) {
        return <Loading />
    }

    return <Component {...data}/>

Typically I would pass in environment={createMockEnvironment()} here, but if I do that it fails because I'm missing offline-specific functions: TypeError: environment.isRehydrated is not a function

If I then try and mock these in a simple way (e.g. returning true for isRehydrated and isOffline) the data returns null.

I saw you had a mock in the codebase here, but this seems to have a lot of complexity I'd rather not replicate.

Is there anything simple I can add as a wrapper around createMockEnvironment that would allow me to fetch data in my tests?

Support of Expo

Hi,
I tried to use your library as an expo react-native project and I have no luck to make it work.

You can check this minimal failing repo - https://github.com/Emetrop/relay-offline-test

This is dump from console

Error: Unable to resolve module `./debugger-ui/debuggerWorker.d9da4ed7` from ``: 

None of these files exist:
  * debugger-ui/debuggerWorker.d9da4ed7(.native|.native.expo.ts|.expo.ts|.native.expo.tsx|.expo.tsx|.native.expo.js|.expo.js|.native.expo.jsx|.expo.jsx|.native.ts|.ts|.native.tsx|.tsx|.native.js|.js|.native.jsx|.jsx|.native.json|.json|.native.wasm|.wasm)
  * debugger-ui/debuggerWorker.d9da4ed7/index(.native|.native.expo.ts|.expo.ts|.native.expo.tsx|.expo.tsx|.native.expo.js|.expo.js|.native.expo.jsx|.expo.jsx|.native.ts|.ts|.native.tsx|.tsx|.native.js|.js|.native.jsx|.jsx|.native.json|.json|.native.wasm|.wasm)
    at ModuleResolver.resolveDependency (/Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:163:15)
    at ResolutionRequest.resolveDependency (/Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/node-haste/DependencyGraph/ResolutionRequest.js:52:18)
    at DependencyGraph.resolveDependency (/Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/node-haste/DependencyGraph.js:282:16)
    at /Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/lib/transformHelpers.js:267:42
    at /Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/Server.js:1305:37
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (/Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/Server.js:99:24)
    at _next (/Users/martin/Work/personal/relay-offline-test/node_modules/metro/src/Server.js:119:9)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)

and screenshot from app
image

so probably it's an issue with not supported AsyncStorage by expo which is explained here right?

react-native-async-storage/async-storage#72

So it means you can't do anything with that and we need to wait for adding support for AsyncStorage by expo?

isLoading seemingly incorrectly returning true for a single render

I have a slightly confusing situation that's causing a problem in a test, but possibly might happen outside of tests also.

My code looks like this (using v5.0.0 of react-relay-offline):

        const {
            data,
            error: _error,
            retry,
            isLoading: relayLoading,
        } = useQuery(options.query, variables, options)

        const isConnected = useIsConnected()
        const isOffline = !isConnected

        if (!isRehydrated) {
            return null
        }

        let networkError = false
        let error = _error
        if (!data && isOffline) {
            // We're offline and have an empty cache: show the network error screen.
            error = new NetworkError()
            networkError = true
        }

        const mostRecent = relayEnv.environment.mock.getAllOperations()[0] //debugging

        console.log(
            `data returned: ${!!data}, `,
            `relayLoading: ${relayLoading}, `,
            `isConnected: ${isConnected}, `,
            `relayOperations: ${relayEnv.environment.mock
                .getAllOperations()
                .map((operation) => operation && operation.request.node.fragment.name)}, `,
            `environment.mock.isLoading: ${
                mostRecent && relayEnv.environment.mock.isLoading(mostRecent)
            }`
        )

        return (
            <Component />

I run one test fine, switching from one test to the other I run:

beforeEach(() => {
        environment.clearCache()
        jest.clearAllMocks()
        jest.useFakeTimers()
    })

    afterEach(async () => {
        jest.runOnlyPendingTimers()
        jest.useRealTimers()
    })

then the second test I get this sequence of logs:

data returned: false,  relayLoading: true,  isConnected: undefined,  relayOperations: HomeScreenQuery,  environment.mock.isLoading: false

data returned: false,  relayLoading: true,  isConnected: true,  relayOperations: HomeScreenQuery,  environment.mock.isLoading: false

data returned: false,  relayLoading: false,  isConnected: true,  relayOperations: HomeScreenQuery,  environment.mock.isLoading: false // <<< This is the problematic render

data returned: false,  relayLoading: true,  isConnected: undefined,  relayOperations: HomeScreenQuery,  environment.mock.isLoading: false

data returned: false,  relayLoading: true,  isConnected: true,  relayOperations: HomeScreenQuery,  environment.mock.isLoading: false

// At this point I run "environment.mock.resolveMostRecentOperation"

data returned: true,  relayLoading: false,  isConnected: true,  relayOperations: ,  environment.mock.isLoading: undefined // <<< At this point we're OK

If I switch the tests round I get the same thing on the second test. So I think there must be some state persisting between the stores (or a mock not being rest somehow)

I appreciate I haven't shown you any of the code around this (I didn't want to overwhelm with information just yet), but are you aware of a situation that could occur where isLoading changes from true -> false even though no data is returned yet, before then loading a couple of cycles later? It looks to me like there's an operation logged in Relay the entire time as well.

Is there anything I could log from the store (or elsewhere) that might help debug this?

I have seen the odd random error screen flash in our app, which makes me wonder if this also happens outside of tests.

TypeError: undefined is not an object (evaluating 'selector.node.name')

Hey, I just tried setting up this project using react native and whenever I make the switch to relay-react-offline my entire app stops working and I get an error screen of death like so:

Screenshot 2020-02-19 at 17 57 00

My code looks something like this:

import {Network} from 'relay-runtime'
import {RecordSource, Store, Environment} from 'react-relay-offline'
import {RelayPaginationProp} from 'react-relay'
import {useCallback} from 'react'

async function fetchQuery(operation: any, variables: Record<string, any>) {
  const response = await fetch('http://localhost:3000/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
  })
  return response.json()
}

const environment = new Environment({
  network: Network.create(fetchQuery),
  store: new Store(new RecordSource()),
})

And my package.json file is like this (I removed the irrelevant parts)

{
  "dependencies": {
    "@react-native-community/async-storage": "^1.8.0",
    "@react-native-community/netinfo": "^5.5.0",
    "react": "16.9.0",
    "react-native": "0.61.5",
    "react-relay": "^9.0.0",
    "react-relay-network-modern": "^4.5.0",
    "react-relay-offline": "^1.1.0",
    "relay-runtime": "^8.0.0",
  }
}

The error seems to arrive from the Store.js file and I get a typescript error also due to a type mismatch with the store property:

Screenshot 2020-02-19 at 17 59 12

The transcript is:

Types of property 'retain' are incompatible.
    Type '(selector: NormalizationSelector, retainConfig?: { ttl?: number | undefined; } | undefined) => Disposable' is not assignable to type '(operation: OperationDescriptor) => Disposable'.
      Types of parameters 'selector' and 'operation' are incompatible.
        Type 'OperationDescriptor' is missing the following properties from type 'NormalizationSelector': dataID, node, variablests(2322)
RelayModernEnvironment.d.ts(32, 14): The expected type comes from property 'store' which is declared here on type 'EnvironmentConfig'

App crashes after updating from Relay 7 to Relay 11 with undefined operation

After upgrading from Relay 7 to Relay 11, we've been having a crash in our app that's quite hard to reproduce.

It seems to happen when we open from the background using a useQuery hook with fetch policy 'store-then-network', so it must be booting from the offline store. Previously we were using a QueryRenderer.

We upgraded from those versions:

    "react-relay": "~7.1.0",
    "react-relay-network-modern": "~4.7.7",
    "react-relay-offline": "~1.1.0",
    "relay-compiler": "~7.1.0",
    "relay-compiler-language-typescript": "~10.1.3",

to the following versions

    "react-relay": "11.0.2",
    "react-relay-network-modern": "~4.7.7",
    "react-relay-offline": "4.0.0",
    "relay-compiler": "11.0.2",
    "relay-compiler-language-typescript": "~14.1.1",
    "@types/relay-runtime": "12.0.0"

Remote Sentry stack trace

Screenshot 2021-11-28 at 13 01 09

Local stack trace

Image from iOS (1)

v0.11.0

  • upgrade of the library that manages the persistence of the stores morrys/wora#11
  • retry should check if environment is online. #20
  • Improve the handling of Loading in react-native #21

createFragmentContainer in v3

We've (finally) tried to upgrade to version 3. It seems we can no longer import createFragmentContainer from react-relay-offline. Has this now been completely removed?

I saw in the release notes that the Query HOC has been removed, but the fragment container is significantly harder for us to migrate off in one go (we'd have to refactor basically every component). Do we need to switch everything to hooks to upgrade to v3 (and hence relay 10)?

react-relay-offline useRestore & state reconciliation

the useRestore hook allows you to manage the restore of data persisted in the storage.
To be used if relay components are used outside of the QueryRenderer or for web applications without SSR & react-native (

const isRehydrated = useRestore(environment);
   if (!isRehydrated) {
     return <Loading />;
   }
  • Example of what happens when using the useQuery/QueryRenderer without useRestore:

  • Example of what happens when using the useQuery/QueryRenderer with useRestore:

    • the application shows a loading and does not render the useQuery component until it is rehydrated
    • if the application is online, the original policy is used and the execute of the QueryFetcher is performed
    • if the application is offline, the policy used will be store-only and the execute will look for the data in the store
  • Example of what happens when you use the useQuery/QueryRenderer without useRestore in SSR applications:

    • the first renderer is executed by setting the store-only policy
    • the store is not empty (having initialized the store with the data recovered from the server. The data recovered from the store will be displayed
    • the state is reconciled between the initial one (recovered from the server) and the state present in the storage, this makes a notification in the store that forces the updating of all the fragments subscribed
    • the restore is resolved (https://github.com/morrys/react-relay-offline/blob/master/src/hooks/useQueryOffline.ts#L59) and the forceUpdate is not executed as data are already displayed previously (priority is given to displaying data returned by status reconciliation)
  • In applications with SSR the useRestore should never be used

With the proposed change in relay-hooks, it will be possible to avoid using the useRestore as it will always be possible to perform a forced execution of the QueryFetcher.

error executing mutations offline without the network parameter

Hello!
I use Create-react-app for the application and thus I use import graphql from "babel-plugin-relay/macro"; because I don't have a customizable .babelrc. The library seems to work one way. It stores everything into the localStorage but when the connection is back it just doesn't do anything

Deprecate relay-hooks in favour of the built-in hooks

Since it was vaguely discussed in #109 , I thought I'd create an issue to guage interest and track progress toward switching over to the built-in hooks.

With relay well into v14 v15 and pushing for suspense and error boundaries, I expect people will want to use this new feature rather quickly. There are already three people who expressed desire for this feature in other issues.

I might be able to find time to help with this process if you're interested.

  • Update offline hooks to use built-ins (includes API changes)
  • Update suspense exemple (still on relay v9)

new release - supports relay v9.0.0

This issue is in progress in the following branches:
react-relay-offline: https://github.com/morrys/react-relay-offline/tree/relay-v9

wora: https://github.com/morrys/wora/tree/relay-v9 (issue morrys/wora#31)

  • optimize store request identifier facebook/relay#2985
    • at the moment I have solved the problem in @ wora/relay-store
  • added tests in wora/relay-store morrys/wora#39
  • added tests in wora/relay-offline
  • added tests in wora/offline-first
  • added tests in wora/netinfo
  • support relay v8.0.0 & v9.0.0
  • upgrade relay-hooks
  • check the compatibility between the query TTL and the new Relay store data invalidation feature
  • added tests in react-relay-offline
  • eslint & prettier
  • CI action
  • release wora/relay-offline & wora/relay-store
  • upgrade wora/relay-offline & wora/relay-store

offline node creation : use clientMutationId instead of id

Hi Morrys,

this is a bit of a question:

If I understood the documentation correct the client creating a new node (offline mutation) needs to provide the node id.

Is it possible to use the relay clientMutationId and let the backend decide on the final id?

Prevent persisting of errors from queries

There might already be some smart solution to this in the library, but I've not been able to find anything on it yet.

We use React Native and persist state to Async Storage. Unfortunately, we're still on an older version of react-relay-offline (1.1.0) so not sure if this question has been resolved later on.

The problem flow is:

  • Let's say we have an error on the server
  • App users open the app. The result of this is persisted to AsyncStorage.
  • We fix the error on the server
  • These users' apps continue to crash because their apps load up the "broken" cache result from async storage

To me it would make sense to either:

  1. Not store query results if there was an error
  2. Clear the Async Storage if there was an app crash

Are there any nice solutions to this built into the library? Or is this something we should implement?

`retry` should check if environment is online.

Occasionally it is useful to have a "Try again" button when we go offline and do not have a cached version. This can be pressed by the user when they come back online in order to retry the query.

In order to do this, the retry prop (from the QueryRenderer) must be used. However, when the device is offline and QR is rendered, retry === null.

A better solution would be to check if the environment is online in every retry function, so that retry is never null. This would be used in useQuery.

For example, in getResult we could do:

if (environment.isOnline()) {
  renderProps.retry = () => {
    setHooksProps(execute(props.environment, props.query, props.variables));
  }
}

fetchQuery results not being added to store cache

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?

release: v1.0.0

  • update dependency "react-relay >= 6.0.0"
  • update dependency "react: >=16.9.0"
  • possibility to use hooks (for now I add the dependency to relay-hooks and create useQueryOffline)
  • fix subscribe store-only & store-or-network #26
  • complete the SSR example in nextjs #23
  • rename the CACHE_FIRST policy in store-or-network
  • better management of the offline configuration morrys/wora#14
  • update dependency wora/cache-persist 2.0.4 (small fix)
  • release relay-hooks 2.0.0 relay-tools/relay-hooks#45

branch react-relay-offline: https://github.com/morrys/react-relay-offline/tree/relay-hooks
branch relay-hooks: https://github.com/relay-tools/relay-hooks/tree/2.0.0
wora: https://github.com/morrys/wora/

useRestore not working anymore after update to v3

Hi, I updated to v3 and running into an issue where I'm stuck in loading state at the boot of my app (react-native).
I'm using the useRestore hook to know if the store is rehydrated as per the docs.
const isRehydrated = useRestore(environment)
I'm now stuck because isRehydrated is always false and I don't see any errors.
I tried to console log the environment after the useRestore call :
Screenshot 2021-01-27 at 17 21 51
There seems to be something going on in promisesRestore, not sure if it's related?
Any idea of what's going on? What can I try?

Potential issue with offline mutation queue / is online logic

Hi!

I've been testing the offline logic around firing mutations in our app, and it seems like the offline mutation queue isn't firing until we hard close our react-native app - then it fires.

When I come online, the queryRenderer completes queries correctly, but otherwise doesn't seem to fire the expected mutations, until later when we hard close the app.

There may be an issue here with the isOnline detection in the library - or maybe I'm not doing something right!

I hope that is helpful.

versions:

"react-relay": "~7.1.0",
"react-relay-network-modern": "~4.4.0",
"react-relay-offline": "~1.1.0",

_this._store.setCheckGC is not a function

I have this error in trying to follow the example of README with React Native.

ERROR  TypeError: _this._store.setCheckGC is not a function. (In '_this._store.setCheckGC(function () {
       return _this.isOnline();
     })', '_this._store.setCheckGC' is undefined)

My Environment:

"react-native": "0.64.2",
"react-relay-offline": "^4.0.0",
"relay-hooks": "^5.0.0",
"@react-native-community/netinfo": "^6.0.2",

Offline Sync Query (DeltaSync)

Hey,

Just looking at the redux persist/hydrate store, and feel as though the naming is a little off here, take a look at the following screenshot.

Screen Shot 2019-05-19 at 22 42 03

I would expect that AppQuery.{....} should contain the root operation, such as AppQuery. allCinemaDetails{ ... }.

[RFC] create a FAQ section

How the react-relay-offline version is managed

  • Patch release ..1 : backward compatible & bug fixes
  • Minor release *.1.0 : backward compatible or small breaking change & new small improvements
  • Major release 1.0.0 : breaking change | new important improvements

I recommend using ~ and not ^ in the react-relay-offline version and follow this repository for any update

What are the main dependencies of react-relay-offline?

react-relay-offline uses these libraries to manage the offline:

  • wora/cache-persist
    • manages the status and its persistence in the storage
  • wora/netinfo
    • manages the network status detection
  • wora/offline-first
    • manages all the offline workflow with queuing requests
  • wora/relay-store
    • extension of the Store and Recordsource objects with the integration of wora/cache-persist + TTL
  • wora/relay-offline
    • extension of the relay environment with the integration of the wora/offline-first library

I recommend following the repository wora for any details.

How should I manage the creation of a new node?

When a new node is created by mutation the id must be generated in the browser to use it in the optimistic response.
This ensures the consistency of the store.

offline needs optimistic updater?

the offline needs to have one of the following options:

  • only optimisticResponse
  • optimisticResponse + updater
  • configs
  • optimisticUpdate

NETWORK_THEN_CACHE policy.

This issue is to discuss how to handle cases where we would like to try to make a network request first before using the store/cache.

This is desirable where we prefer up-to-date information, but will settle for out of data information. It seems like a common use case for offline apps, making it worth at least discussing in the context of this library.

An example might be trying to fetch comments. We don't want to show the old comments if the new ones are available, but rather want to try to fetch the latest comments first, but are happy to default to showing the old ones if necessary.

Array Warning: The record contains two instances of the same id

Hi,

I'm querying some fields and get the following warning:

Warning: RelayResponseNormalizer: Invalid record. 
The record contains two instances of the same id: 
`RW50bmFobWVwcm90b2tvbGxQYXJhbWV0ZXI6Njc=` with conflicting field, optionen and its values: nein,ja and nein,ja. If two fields are different but share the same id, one field will overwrite the other.

So far I am seeing this warning only for fields defined in Graphql as array of strings ([String]), and independent of the content.
An empty array [] throws the same error as an array of ["nein", "ja"]

The warning does not occur in 'store-only' mode.

Include an implementation of fetchQuery to rehydrate store.

react-relay's fetchQuery function does not work well with the store, causing RelayRecordSourceMutator errors:

RelayRecordSourceMutator#create(): Cannot create a record with id 

To fix this, this library should implement a modified version of fetchQuery which rehydrates the store before making the query.

Something like this seems to do the job:

import { fetchQuery as relayFetchQuery } from 'react-relay-offline'

async function fetchQuery(...args) {
    const [environment] = args

    if (!environment.isRestored()) {
        await environment.restore()
    }

    return relayFetchQuery(...args)
}

Allow offline execution of mutations that do not modify the store

I've just upgraded from 0.11.1 -> 1.0.2 (and upgraded Relay to v6 - I've also tried v7).

When I turn my connection off to test offline, on one screen I get this error TypeError: Cannot read property 'toJSON' of undefined from the OfflineFirstRelay file in relay-offline.

It comes from this line:

var sinkPublish = environment
                    .getStore()
                    .getSource()
                    ._sink.toJSON();

This only seems to happen on one screen, I have done some digging and can't see specifically why this would error and other screens not. I can dive into this further, but just wanted to see if there was anything obvious I've missed in the upgrade.

My relay-offline library in my yarn.lock is at the right version "@wora/relay-offline@^2.0.4"

I have also followed your react-native example (e.g. I've added useRestore above my QueryRenderer).

Refetch containers

Noticed on your README.md you said:

TODO:
Implementation of Refetch Container Offline

Just wondering if you could expand a little more on what you think the library is missing to support Refetch Containers.

I have used createRefetchContainer in my native offline example app and seems to work ok from a first pass.

See container here: https://github.com/robertpitt/react-relay-offline-native-example/blob/feature/fragment-container-v0.2.0/src/pages/Cinimas/index.ts, ui code in Cinimas.tsx.

Pass an `offline` prop to QueryRenderer render function.

Much like the cached: boolean prop is passed to the render function of the QueryRenderer, it would be useful to have a offline: boolean prop that indicates whether the environment is offline.

Although this can be done with environment.isOffline(), this means users depend on this being the same logic as is used by the lookupInStore function so they are bound to a specific version of this library.

Cached QueryRender refreshing

Hi,

I'm just testing the library (it sounds perfect), but have some strange behavior I do not understand:

When I'm using any offline mode, the QueryRenderer does not update the view correctly. Using a network request works just fine.

Basically only 'Loading' is shown, as if the props never populate (but the log shows differently)

Here is the sample I am using:

import React from 'react';
import './App.css';
import _ from 'lodash';

import ReactEnvironment from './ReactEnvironment';
import {NETWORK_ONLY, STORE_ONLY, STORE_OR_NETWORK, STORE_THEN_NETWORK} from 'react-relay-offline';


import graphql from 'babel-plugin-relay/macro';
import {QueryRenderer} from 'react-relay-offline';


function App() {
  return (
    <div className="App">
      <header className="App-header">
      <QueryRenderer
          environment={ReactEnvironment}
          fetchPolicy={'store-or-network'}
          variables={{}}
          query={graphql`
            query AppQuery {
              auftraggeber {
                edges {
                  node {
                    id
                    name
                  }
                }
              }
            }
          `}
          render={({props, error, retry, cached}) => {
            console.log('props')
            console.log(props)
            console.log('cached')
            console.log(cached)
            if (props) {
              let rows = [];
              _.each(props.auftraggeber.edges, (ele) => {
                rows.push(<p key={ele.node.id}>{ele.node.name} </p>);
              })
              console.log('returning rows..')
              console.log(rows)
              return <div>{rows}</div>;
            } else {
              return <div>Loading</div>;
            }

            // return (
            //   <button onClick={retry} className="refetch">
            //     Retry
            //   </button>
            // );
          }}
        />

      </header>
    </div>
  );
}

export default App;


The logging comes out a bit weird as well (pure offline log here) showing cached false in the end:
props

App.js:37 null
App.js:40 cached
App.js:41 false
App.js:36 props
App.js:37 null
App.js:40 cached
App.js:41 false
ReactEnvironment.js:45 start offline []
ReactEnvironment.js:50 finish offline undefined []
App.js:36 props
App.js:37 {auftraggeber: {…}}auftraggeber: edges: (2) [{…}, {…}]0: {node: {…}}1: {node: {…}}length: 2__proto__: Array(0)proto: Object__proto__: Object
App.js:40 cached
App.js:41 true
App.js:48 returning rows..
App.js:49 (2) [{…}, {…}]
App.js:36 props
App.js:37 {auftraggeber: {…}}auftraggeber: edges: (2) [{…}, {…}]proto: Object__proto__: Object
App.js:40 cached
App.js:41 false
App.js:48 returning rows..
App.js:49 (2) [{…}, {…}]

React Native Support

Hey Lorenzo,

Amazing work so far, I have been thinking about an offline mode in our react native application for a little while now and during the design that your library takes is very similar to what we came up with.

We have been doing some interesting stuff using this approach, such as seeding data into the store via fetchQuery and doing some post store updates to simulate root queries, allowing us to perform initial sync and then be able to function completely offline by optimistically adding root nodes to allow offline mode for seeded data rather than just a rolling cache.

I haven't run this project as of yet but I have read over the code, I am just wondering about your choice for using idb and it's a potential incompatibility with react-native.

Store doesn't persist data after rebuilding it when logging out

When logging out, we rebuild a new environment like this:

    const store = new Store(recordSource, persistOptionsStore, relayStoreOptions)
    const env = new Environment(
        {
            network,
            store,
        },
        {}
    )

but the issue is that mutations aren’t updating the store until we hard restart the app.

Is there anythign specific we need to do when logging out?

environment.isRehydrated is not a function

There's something really strange going on here.

In standard react-relay the fetchQuery function that goes into the Network constructor has the following structure:

function fetchQuery(
  operation,
  variables,
  cacheConfig,
)

The fetchQuery that comes with react-relay-offline expects the following:

function fetchQuery(environment, taggedNode, variables, cacheConfig)

So what am I doing wrong here? It's something with the fetchQuery for sure, but it looks to me like your documentation is not complete (There's no clarity where fetchQuery is coming from).

Mutations Process

Hello,

I have a scenario that needs to be covered by our offline mutation solution and wanted to understand if the current implementation fits our needs.

We have the following requirements:

  1. Mutations should be persisted before they are executed to support eventual consistency.
  2. Mutations must be retried with original headers ( preserve multi-user devices )
  3. The original timestamp must also be sent to the server ( for conflict resolution ).

Just wondering if you can discuss a little detail on how this implementation stacks up to those requirements, and if not, what would need to be done for it be able to support them.

Look forward to the discussion.

Rob.

Multiple calls of useMutation into offline mode

Hi, I have a doubt about what happens when I have multiple mutations happening into offline mode and the internet goes back online. Today I try to use the useMutation with this configuration and only the last mutation made into offline mode is synced with the server when internet goes back online. I need extra configurations to have multiple mutations into the queue to be performed when internet goes back online?

const [updateStoreCategory] = useMutation<StoreCategoryListUpdater_Mutation>(
  graphql`
    mutation StoreCategoryListUpdater_Mutation(
      $addConnections: [ID!]!
      $deleteConnections: [ID!]!
      $input: UpdateStoreCategoryInput!
    ) @raw_response_type {
      updateStoreCategory(input: $input) {
        store
          @appendNode(
            connections: $addConnections
            edgeTypeName: "StoreEdge"
          ) {
          id @deleteEdge(connections: $deleteConnections)
          category
        }
      }
    }
  `
)

const update = () => {
  updateStoreCategory({
    variables: {
      input: {
        storeId: store.id,
        category: newCategory,
      },
      deleteConnections,
      addConnections: [
        getSafeConnectionID('root', 'StoreList_stores', {
          where: { category: { eq: newCategory } },
        }),
      ],
    },
    optimisticResponse: {
      updateStoreCategory: {
        store: {
          id: store.id,
          category: newCategory,
        },
      },
    },
  })
}

source.getRecordIDs is not a function

Thanks for the amazing lib!

I've just tried upgrading from 0.10.0 -> 0.11.1. I'm using react-relay 5.0.0.

I'm now getting an error:

source.getRecordIDs is not a function

From RelayModernStore.

Any idea what this could be?

new release - supports netinfo 5.x

I tried to setup my relay environment in a react-native app by importing Environment, Store & RecordSource from react-relay-offline (no particular options). Now when I import QueryRenderer from react-relay-offline, I'm getting this error: Cannot read property 'fetch' of undefined

Seems to happen in OfflineFirst.js at this line :
this.promisesRestore = Promise.all([netinfo_1.NetInfo.isConnected.fetch(), this.offlineStore.restore()])

As far as I understand, the web implementation of NetInfo has a fetch function on isConnected but the native NetInfo doesn't have the same interface (anymore?).

Is PersistGate required.

Does this solution still require the need for PersistGate to make sure the hydrate is finished before the QueryRenderers's are mounted?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.