-
Notifications
You must be signed in to change notification settings - Fork 91
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
Add ability to update the cache after a mutation #52
Comments
I've been thinking about this + auto refetching after a mutation. Here are the three methods that's been suggested so far:
updateCache
Usageimport { LIST_USERS_QUERY } from "./somewhere";
const CREATE_USER_MUTATION = `...`;
function CreateNewUser() {
const [createUser] = useMutation(CREATE_USER_MUTATION, {
updateCache: (cache, result) => {
const cacheKey = client.getCacheKey({ query: LIST_USERS_QUERY });
const existingResult = cache.get(cacheKey);
cache.set(cacheKey, {
...existingResult,
data: {
users: [...existingResult.data.users, result.data.createUser]
}
});
}
});
} Implementation// sumarised useClientRequest.js
fetchData() {
return this.client.request(operation, options).then(result => {
if (options.updateCache) {
options.updateCache(cache, result)
}
})
} For this to work correctly we need a way to re-render components that would be affected by the cache change. Adding an event emitter interface to the
For the component to re-render, we need an additional // summarised useQuery.js
const [queryReq, state] = useClientRequest(query, allOpts);
React.useEffect(() => {
if (!client.cache || !client.cache.on) {
return;
}
// This implies we'd also store the cacheKey in the
// useClientRequest state
client.cache.on(state.cacheKey, queryReq);
client.cache.on("CACHE_CLEARED", queryReq);
return () => client.cache.off(state.cacheKey, queryReq);
}, [state.cacheKey]); refetchQueries
This is a feature that developers are familiar with in Apollo:
There is an additional option
usageSimple usage with no variables: import { LIST_USERS_QUERY } from "./somewhere";
const CREATE_USER_MUTATION = `...`;
function CreateNewUser() {
const [createUser] = useMutation(CREATE_USER_MUTATION, {
refetchQueries: () => [LIST_USERS_QUERY]
});
} Usage with variables: import { LIST_USERS_QUERY } from "./somewhere";
const CREATE_USER_MUTATION = `...`;
function CreateNewUser() {
const [createUser] = useMutation(CREATE_USER_MUTATION, {
refetchQueries: () => [
{
query: LIST_USERS_QUERY,
variables: {
limit: 3
}
}
]
});
} ImplementationCouple ways to do this:
// sumarised useClientRequest.js
fetchData() {
return this.client.request(operation, options).then(result => {
if (options.refetchQueries) {
const queries = options.refetchQueries(result)
const refetchingPromise = Promise.all(queries.map(({ query, options }) => {
const cacheKey = client.getCacheKey(query, options)
const refetch = client.queries[cacheKey]
return refetch()
}))
if (options.awaitRefetchQueries) {
await refetchingPromise
}
}
})
} refetchAfterMutations
UsageHere's the simple use case, with no variables: import { ADD_TODO_MUTATION } from "../somewhere";
const GET_ALL_TODOS_QUERY = `...`;
function TodosList() {
const { loading, error, data } = useQuery(GET_ALL_TODOS_QUERY, {
refetchAfterMutations: [ADD_TODO_MUTATION]
});
} Usage with variables Here we only want to refetch if the import { UPDATE_USER_MUTATION } from "../somewhere";
const GET_USER_QUERY = `...`;
function UserDetails({ userId }) {
const { loading, error, data } = useQuery(GET_USER_QUERY, {
refetchAfterMutations: [
{
mutation: UPDATE_USER_MUTATION,
filter: result => result.userId === userId
}
]
});
} Implementation// summarised useQuery.js
function useQuery(query, options) {
const mutationHandlers = useRef({})
useEffect(() => {
if (options.refetchAfterMutations) {
// probably best to map them to be all objects
// with { mutation, filter }
options.refetchAfterMutations.forEach((mutation) => {
const mutationEvent = typeof mutation === string
? mutation
: mutation.mutation
const handler = (result) => {
if (mutation.filter(result)) refetch()
}
mutationHandlers.current[mutation] = handler
// we either need a separate event emitter
// or share the event emitter between client + cache
client.on(mutation, handler)
})
// clean up listeners
return () => {
// iterate over mutationHandlers
// client.off(event)
}
}
})
} Overall thoughtsCacheKeyCurrently, the cacheKey is an object from To make cache.on = (cacheKeyObj, listener) =>
emitter.on(generateKey(cacheKeyObj), listener); The reason the cache was responsible for hashing the object was:
How this relates to the new features
Possible Changes
Shared event emitterIf we decided to add support for It'd be ince to be able to share the same emitter across Possible Changes
SummaryThis just a brain dump and completely open to discussion; I'd like to hear what peoples thoughts and opinions are on the matter :) |
@bmullan91 I can give option 3 a try and send a PR, is that ok? |
I've been playing with the library trying to implement |
@olistic could you write down your thoughts / share your WIP, then what your questions are. It'll be worthwhile capturing this info + responses to document why changes have been done for others who come to this issue at a later date. After that then we could arrange a call 👍 |
This isn't resolved/intentionally closed it seems, so reopening. |
@salmanm this was closed automatically by the stale action we introduced a short while ago to begin shrinking down the list of open issues and PRs which didn't receive any activity in a long time. There is a chance the stale action will close this again if there is no activity, but let's keep an eye on it. Thanks for spotting it. |
Closed by #686 |
Currently the only way to update a record after a mutation is to refetch the entire query.
The text was updated successfully, but these errors were encountered: