Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can environment.retain() be used with the network layer to disable relay garbage collection? #114

Open
a-tokyo opened this issue Aug 20, 2020 · 4 comments

Comments

@a-tokyo
Copy link
Contributor

a-tokyo commented Aug 20, 2020

Description

How can we achieve using relay's environment.retain() to retain the data of a certain query and disable garbage collection?

Reasoning:

  • "If no component is rendering the local data and you want to manually retain it, you can do so by calling environment.retain()" - Relay docs

Example from relay documentation:

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

// Create a query that references that record
const localDataQuery = graphql`
  query LocalDataQuery {
    viewer {
      notes {
        __typename
      }
    }
  }
`;

// Create an operation descriptor for the query
const request = getRequest(localDataQuery);
const operation = createOperationDescriptor(request, {} /* variables */);


// Tell Relay to retain this operation so any data referenced by it isn't garbage collected
// In this case, all the notes linked to the `viewer` will be retained
const disposable = environment.retain(operation);


// Whenever you don't need that data anymore and it's okay for Relay to garbage collect it,
// you can dispose of the retain
disposable.dispose();
@felippepuhle
Copy link
Member

I don't know if handling this on the network layer is a good idea.

How we'd know which operation we should retain or dispose?
I mean, we can't retain all queries forever.

@a-tokyo
Copy link
Contributor Author

a-tokyo commented Aug 21, 2020

@felippepuhle

We can implement it as a feature on top of the QueryRenderer or fetchWithMiddleware function (relay's fetch func) (eg: a ttl). The query is then retained for the ttl period. If the ttl provided to the QueryRenderer is larger than the TTL for the cache we'd never face the issues were the store was updated and the network cache overwrote these updates.

How we'd know which operation we should retain or dispose?

  • We only retain queries that have ttl -- we dispose the Observables after the ttl expires

Assume this scenario:

  • We have a connection of messages that we render via a query renderer
  • We update the connection of messages via subscriptions
  • The Messages Query renderer is unmounted AND mounted again
  • The Messages Query renderer now uses the old query data that is store in the network layer's cache and doesn't render the data updated by the subscriptions. Since relay modern garbage collected the connection along with it's updates.

Note:
react-relay-offline provides good example use cases

@felippepuhle
Copy link
Member

Oh, understood! Thanks for the explanation @a-tokyo! It seems a really cool idea.

@a-tokyo
Copy link
Contributor Author

a-tokyo commented Aug 21, 2020

@felippepuhle Awesome!! (:

I was checking the codebase to see how I can implement it but faced a couple of issues that I hope I can overcome soon:

  • How to pass data from the Query Renderer (the ttl option)
  • How to get an environment instance to call retain on

example of how the retain code should look like:

/* @flow */
import { createOperationDescriptor, getRequest } from 'relay-runtime';

/**
 * Retains data in the relay store to avoid garbage collection
 *
 * useful in case of subscriptions
 *
 * https://stackoverflow.com/questions/58022925/disable-relayjs-garbage-collection
 */
const retainInStore = ({
  getEnvironment,
  query,
  variables,
}: {
  getEnvironment: () => Environment,
  query: string,
  variables?: Object,
}): Disposable => {
  // Create a relay request from the query
  const request = getRequest(query);
  // Create an operation descriptor for the query
  const operation = createOperationDescriptor(request, variables);
  // Tell Relay to retain this operation so any data referenced by it isn't garbage collected and return the disposable
  return getEnvironment().retain(operation);
};

export default retainInStore;
if (opts.ttl) {
  const { dispose } = retainInStore({ getEnvironment, query, variables });
  const timeout = setTimeout(() => { dispose() }, ttl);
  // we can call clearTimeout(timeout); when we want to cleanup
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants