Skip to content

Instantly share code, notes, and snippets.

@ngryman
Created June 29, 2019 17:24
Updating cached data from multiple parameterized queries after a mutation (hacky solution)
addTask({
variables: { input },
optimisticResponse: {
addTask: {
__typename: 'Task',
id,
...input
}
},
update: (proxy: any, { data: { addTask } }: any) => {
const query = Queries.tasks
const variablesList = getVariablesListFromCache(proxy, query)
for (const variables of variablesList) {
const data = proxy.readQuery({ query, variables })
data.tasks.push(addTask)
proxy.writeQuery({ query, data })
}
}
})
const getVariablesListFromCache = (proxy: any, query: any) => {
const queryName = query.definitions[0].name.value
const rootQuery = proxy.data.data.ROOT_QUERY
// XXX: When using `optimisticResponse`, `proxy.data.data` resolves to
// another cache that doesn't contain the root query.
if (!rootQuery) return []
const matchQueryReducer = (names: string[], name: string) => {
if (name.startsWith(queryName)) {
names.push(name)
}
return names
}
const parseQueryNameToVariables = (name: string) =>
JSON.parse((name.match(/{.*}/) as string[])[0])
return Object.keys(rootQuery)
.reduce(matchQueryReducer, [])
.map(parseQueryNameToVariables)
}
@riccoski
Copy link

Interesting, does it only add filters (from the @connection directive) as that could be a better approach

@eric-burel
Copy link

Awesome, I am trying this out. First remark, the startsWith is not the best strategy because it could match more queries than expected. Instead a regex could allow to match more precisely, eg "customers" would match "customers({ foobar })" but not "customersSpecificQueryYouDontWantToMatch"

@eric-burel
Copy link

Also I don't know why but in the cache, it's not the query name that is used but the underlying result :

Simplified example:

query multiCustomersQuery {
    customers {
    }
}

In this case I need to match customers which is in the cache, but your code will match multiCustomersQuery.

@nihey
Copy link

nihey commented May 21, 2020

In this case I need to match customers which is in the cache, but your code will match multiCustomersQuery.

@eric-burel I've felt this exact same thing, for me the solution that works best would be to use:

const queryName = query.definitions[0].selectionSet.selections[0].name.value

Instead of:

const queryName = query.definitions[0].name.value

Even with this, it would not handle queries that contains multiple subqueries below it:

query multiCustomersQuery {
    customers {
    }
    anotherThing {
    }
}

For my use case there are no such cases, so I do not need to worry, but it is good to warn about it.

@carlos-talavera
Copy link

In case it helps someone, I used the selectionSet since I use different names than the declared in the resolvers for the queries in the frontend. To avoid updating queries that are not the ones that I want I ended up using a simple regex, for example, if the query is lead, but I also have leads, I only want lead. This works for me:

const getVariablesListFromCache = (proxy: any, query: any) => {
    const queryName =
      query.definitions[0].selectionSet.selections[0].name.value;
    const rootQuery = proxy.data.data.ROOT_QUERY;

    if (!rootQuery) return [];

    const matchQueryReducer = (names: string[], name: string) => {
      const regex = new RegExp(`${queryName}\\(.*`);

      if (regex.test(name)) {
        names.push(name);
      }
      return names;
    };

    const parseQueryNameToVariables = (name: string) =>
      JSON.parse((name.match(/{.*}/) as string[])[0]);

    return Object.keys(rootQuery)
      .reduce(matchQueryReducer, [])
      .map(parseQueryNameToVariables);
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment