Comments (14)
At a superficial level, async read
functions are not possible because the cache.read{Query,Fragment}
methods are synchronous, so everything involved in reading a query from the cache must also be synchronous. Reading from the cache needs to be synchronous because many UI frameworks (including React, at least without Suspense) have synchronous rendering pipelines, so it's important for UI components to have immediate access to any existing data.
However, you can implement a read
function that is effectively asynchronous using reactive variables and the options.storage
object:
new InMemoryCache({
typePolicies: {
Person: {
fields: {
asyncName: {
read(existing, { storage }) {
if (!storage.var) {
storage.var = makeVar("unknown");
delay(100).then(() => storage.var("Foo"));
}
return storage.var();
}
}
}
}
}
})
Although this may seem a bit convoluted compared to an async
function, it has a fairly significant advantage: once options.storage.var
has been updated, all future reads can return the correct value immediately, whereas an async
function would need to be called repeatedly, and would return a new Promise
every time.
To make this pattern simpler, we believe we may be able to provide a helper function that would work something like this:
new InMemoryCache({
typePolicies: {
Person: {
fields: {
asyncName: {
read: asyncRead(async (existing, options) => {
await delay(100);
return "Foo";
}),
}
}
}
}
})
The asyncRead
helper function (to be implemented) would return a read
function that behaves essentially like the makeVar
/storage
code I showed above. If you're feeling industrious/impatient, you might try writing it yourself. Happy to answer questions here if you get stuck.
from apollo-feature-requests.
i am unable to get this to work 😢
i want to read data from my asyncStorage.
when I console log within the typePolicies object, I get the correct output
within my component where I want to use it I get always undefined
maybe a clear documentation or prepared helper function could help
from apollo-feature-requests.
Hey @benjamn, thanks for your response! I think I finally get why read
calls have to be sync in Apollo Client.
The asyncRead
helper's API looks kinda neat. However, if I understand your code correctly there will be a certain time frame where components trying to read this field via useQuery
will get undefined
which could be unwanted and quite confusing.
Example:
const client = new ApolloClient({
client: new InMemoryCache({
typePolicies: {
Person: {
fields: {
asyncName: {
read: asyncRead(async (existing, options) => {
await delay(100);
return "Foo";
}),
},
},
},
},
}),
});
// Somewhere in a React tree under ApolloProvider
const Component = () => {
const { data, loading, error } = useQuery(gql`
query ComponentQuery {
people {
name,
asyncName @client
}
}
`)
if (loading) {
return <Loading />
}
return (
<div>
{data.name} {/** <- this will be defined */}
{data.asyncName} {/** <- this still might be `undefined` right after `loading` flips from `true` to `false` */}
</div>
);
}
What I'd imagine that helper would let me do is somehow teach the client to wait for all data (remote and local) to be resolved and only then render, but unfortunately this seems not to be the case.
I can't help but wonder, perhaps my use case is a bit niche and maybe it's better off handled in a custom link
where I could extend schema in a similar fashion as in v2. But for the time being I think I'll be sticking to local resolvers for now 😢
from apollo-feature-requests.
Nevermind. I figured out. Instead of makeVar(null)
u should use makeVar(undefined)
. Here is an example of asyncRead function
import { makeVar } from '@apollo/client'
export const asyncRead = (fn, query) => {
return (_, args) => {
if (!args.storage.var) {
args.storage.var = makeVar(undefined)
fn(_, args).then(
data => {
args.storage.var(data)
args.cache.writeQuery({
query,
data: { [args.fieldName]: data }
})
}
)
}
return args.storage.var()
}
}
and then loading state will detect properly.
from apollo-feature-requests.
@gcofficial I have an async problem with a query field and I'm wondering if your asyncRead could help.
Would be awesome if you could tell me how I would wrap this in your proposed asyncRead function:
Query: { fields: { async userTest (_, { variables, toReference }) { const userTest = await datasource.get(variables.id) return toReference({ __typename: 'UserTest', ...userTest }, true) } } }
Hey 👋🏻 @MarMun
It will be something like this...
Query: {
fields: {
userTest: asyncRead(() => {}, query)
}
}
from apollo-feature-requests.
What I'd imagine that helper would let me do is somehow teach the client to wait for all data (remote and local) to be resolved and only then render, but unfortunately this seems not to be the case.
I'm in a similar situation myself and the best solution I've came up so far with is, instead of asyncName
returning either a string or an undefined make it return an object with the loading
status as below:
{
name, // A string
loading // A boolean
}
When the loading
would flip from true
to false
the client will know that the final value is delivered to the name
filed. So it's like a loading
status of a GraphQL query, but on a field level. It's somewhat cumbersome for my taste but better than nothing. At least it allows to understand when the results are delivered exactly.
from apollo-feature-requests.
At a superficial level, async
read
functions are not possible because thecache.read{Query,Fragment}
methods are synchronous, so everything involved in reading a query from the cache must also be synchronous. Reading from the cache needs to be synchronous because many UI frameworks (including React, at least without Suspense) have synchronous rendering pipelines, so it's important for UI components to have immediate access to any existing data.However, you can implement a
read
function that is effectively asynchronous using reactive variables and theoptions.storage
object:new InMemoryCache({ typePolicies: { Person: { fields: { asyncName: { read(existing, { storage }) { if (!storage.var) { storage.var = makeVar("unknown"); delay(100).then(() => storage.var("Foo")); } return storage.var(); } } } } } })Although this may seem a bit convoluted compared to an
async
function, it has a fairly significant advantage: onceoptions.storage.var
has been updated, all future reads can return the correct value immediately, whereas anasync
function would need to be called repeatedly, and would return a newPromise
every time.To make this pattern simpler, we believe we may be able to provide a helper function that would work something like this:
new InMemoryCache({ typePolicies: { Person: { fields: { asyncName: { read: asyncRead(async (existing, options) => { await delay(100); return "Foo"; }), } } } } })The
asyncRead
helper function (to be implemented) would return aread
function that behaves essentially like themakeVar
/storage
code I showed above. If you're feeling industrious/impatient, you might try writing it yourself. Happy to answer questions here if you get stuck.
Hey @benjamn
I tried your approach but there is another problem... Why the data which was received asynchronously will not record into cache?
As u can see my cache is empty after all these manipulations...
from apollo-feature-requests.
from apollo-feature-requests.
Should I do it manualy? Right?
Like this:
if (!storage.var) {
storage.var = makeVar(null);
get(`performer/performers?${urlQuery}`)
.then(
res => res.map(
el => ({
...el,
id: el.performerId,
__typename: 'performers'
})
)
)
.then(
data => {
storage.var(data)
cache.writeQuery({
query: getPerformers,
data: {performers: data}
})
}
)
}
return storage.var();
from apollo-feature-requests.
@benjamn And how I can set the loading
state to true in this case? It's false always for me.
from apollo-feature-requests.
@gcofficial I have an async problem with a query field and I'm wondering if your asyncRead could help.
Would be awesome if you could tell me how I would wrap this in your proposed asyncRead function:
Query: {
fields: {
async userTest (_, { variables, toReference }) {
const userTest = await datasource.get(variables.id)
return toReference({
__typename: 'UserTest',
...userTest
}, true)
}
}
}
from apollo-feature-requests.
Is there a way around this for the mean time?
Rather than trying to read from async storage within the cache, is it possible to use something like apollo-cache-persist so that the whole cache just gets saved to AsyncStorage and persists over app reloads?
from apollo-feature-requests.
I attempted to migrate some resolvers over to type policy read
and merge
functions. My thoughts:
EDIT: I can use Mutation field policiesFieldFunctionOptions
need to include the name of theQuery
orMutation
that caused theread
ormerge
. Particularly withMutations
(but occasionally with reads as well) the behavior of interacting with local state can change and resolvers can handle this.read
andmerge
properties need to support async functions or Apollo should provide a wrapper like the example above.read
andmerge
functions need type safe examples (includingasync
examples).- Migration documentation will be needed in advance before resolvers can be removed.
For now I've reverted my changes and will have to stick with resolvers. Although, I've seen how the flexibility of local resolvers can be abused and agree restrictive read
and merge
type policies have merit.
from apollo-feature-requests.
Commenting here in case anybody else needs this. I recently found that returning a reactive var with undefined
no longer results in useQuery
loading while it resolves to a value. Additionally, returning the reactive var no longer updated when the value was updated from the cache. To fix this, we did something that wasn't ideal but it worked:
import { ReadFieldFunction } from '@apollo/client/cache/core/types/common';
// this makes the cache from a field-read
const cacheId = (readField: ReadFieldFunction): string => `${readField('__typename')}:${readField('id')}`
// in your type policies
const typePolicies = {
Query: {
fields: {
SomeType: {
fields: {
yourAsyncField: {
read(existing, { readField, cache }) {
doSomethingAsync().then((result) => {
cache.modify({
id: cacheId(readField),
fields: {
yourAsyncField: result,
},
});
});
return existing;
},
},
},
},
},
},
};
We modify the cache directly on the object (assuming your field has an id
and a typename
). This does mean that you can get a flash of content before this happens, but we've handled that by temporarily returning the value as loading
.
from apollo-feature-requests.
Related Issues (20)
- Remove React dependency when importing gql from @apollo/client HOT 2
- Support `select` QueryHookOptions in `useQuery`
- @defer timeout in clients HOT 3
- Allow passing in a client object to the `useFragment` hook HOT 2
- Remove "React" from the docs and keep it generic HOT 1
- Support skipToken in useReadQuery HOT 6
- Support for a Cache type policy when field is Garbage Collected HOT 2
- Add cache eviction of a query via query DocumentNode HOT 1
- Run a callback before the internal state of useMutation is updated.
- Local Resolvers: Support returning errors and data together for partial results
- Dependency Dashboard
- Revisit `refetchQueries` API
- Add an option to MockProvider to throw when a request fails to match a mock
- Please add polyfill for `globalThis` as it breaks compatibility for Chrome < v71 HOT 2
- Refetch on window focus HOT 2
- Drop `ApolloClient.query()` in next major HOT 1
- Keyword search in Job postings filter HOT 1
- warnAboutDataLoss fails with stringifying error and breaks application
- Query consolidation HOT 1
- Compare variables based on their serialized value HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from apollo-feature-requests.