Apollo Client's evict from cache but never re-render - caching

I'm using Apollo's Client and trying to evict the address object from the cache.
Using evict does remove it from the cache (confirmed by Apollo Client Dev Tools), however, it looks like the component does not rerender and the address would still be there unless I refresh the page.
deleteAddress({
variables: { id: data.address.id },
update: (cache) => {
cache.evict({
id: "Address:" + data.address.id,
});
cache.gc();
},
})
}
Specifically, I tried the following 2 methods, both removed the object from the cache but neither re-render.
cache.evict({
id: "Address:" + data.address.id,
});
cache.gc();
cache.evict({
id: cache.identify(data?.address),
});

Related

Unable to clear apollo-client cache on logout

I have read through a couple other posts as well as a few github issues, and I am yet to find a solution. When I logout as one user, and sign in as a different user, the new user will appear for a split second and then be replaced by the previous user's data.
Here is my attempt to go nuclear on the cache:
onClick={() => {
client
.clearStore()
.then(() => client.resetStore())
.then(() => client.cache.reset())
.then(() => client.cache.gc())
.then(() => dispatch(logoutUser))
.then(() => history.push('/'));
}}
I've tried getting the client object from both these locations (I am using codegen):
const { data, loading, error, client } = useUserQuery();
const client = useApolloClient();
Here is my Apollo client setup:
const apolloClient = new ApolloClient({
uri: config.apiUrl,
headers: {
uri: 'http://localhost:4000/graphql',
Authorization: `Bearer ${localStorage.getItem(config.localStorage)}`,
},
cache: new InMemoryCache(),
});
When I login with a new user, I writeQuery to the cache. If I log the data coming back from the login mutation, the data is perfect, exactly what I want to write:
sendLogin({
variables: login,
update: (store, { data }) => {
store.writeQuery({
query: UserDocument,
data: { user: data?.login?.user },
});
},
})
UserDocument is generated from codegen:
export const UserDocument = gql`
query user {
user {
...UserFragment
}
}
${UserFragmentFragmentDoc}`;
Following the docs, I don't understand what my options are, I have tried writeQuery, writeFragment, and cache.modify and nothing changes. The Authentication section seems to suggest the same thing I am trying.
Seems like all I can do is force a window.location.reload() on the user which is ridiculous, there has to be a way.
Ok, part of me feels like a dumb dumb, the other thinks there's some misleading info in the docs.
despite what this link says:
const client = new ApolloClient({
cache,
uri: 'http://localhost:4000/graphql',
headers: {
authorization: localStorage.getItem('token') || '',
'client-name': 'Space Explorer [web]',
'client-version': '1.0.0',
},
...
});
These options are passed into a new HttpLink instance behind the scenes, which ApolloClient is then configured to use.
This doesn't work out of the box. Essentially what is happening is my token is being locked into the apollo provider and never updating, thus the payload that came back successfully updated my cache but then because the token still contained the old userId, the query subscriptions overwrote the new data from the new user's login. This is why refreshing worked, because it forced the client to re-render with my local storage.
The fix was pretty simple:
// headerLink :: base headers for graphql queries
const headerLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// setAuthorizationLink :: update headers as localStorage changes
const setAuthorizationLink = setContext((request, previousContext) => {
return {
headers: {
...previousContext.headers,
Authorization: `Bearer ${localStorage.getItem(config.localStorage)}`,
},
};
});
// client :: Apollo GraphQL Client settings
const client = new ApolloClient({
uri: config.apiUrl,
link: setAuthorizationLink.concat(headerLink),
cache: new InMemoryCache(),
});
And in fact, I didn't even need to clear the cache on logout.
Hope this helps others who might be struggling in a similar way.

How to work with both mocked graphql API and an externally served GraphQL endpoint

I'm hoping to hear some inputs from the experts here.
I'm currently working on NextJS project and my graphql is running on mocked data which is setup in another repo.
and now that the backend is built by other devs were slowly moving away from mocked data to the real ones.
They've given me an endpoint to the backend where I'm supposed to be querying data.
So the goal is to make both mocked graphql data and the real data in backend work side by side at least until we fully removed mocked data.
So far saw 2 ways of doing it, but I was looking for a way where I could still use hooks like useQuery and useMutation
Way #1
require('isomorphic-fetch');
fetch('https://graphql.api....', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: `
query {
popularBrands ( storefront:"bax-shop.nl", limit:10, page:1){
totalCount
items{id
logo
name
image
}
}
}`
}),
})
.then(res => res.json())
.then(res => console.log(res.data));
Way #2
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
});
async function test () {
const { data: Data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
mission_patch
}
rocket {
rocket_name
}
}
}
`
});
console.log(Data)
}
Pseudo code:
Query the real data first
check if its empty, if it is, query the mock data.
If both are empty, then it's really an empty result set.
You can write a wrapper around the hooks you use that does this for you so you don't have to repeat yourself in every component. When you're ready to remove the mocked data you just remove the check for the second. data set.
This is a common technique when switching to a new database.

Unsupported content type with GraphIql apollo engine

I'm running apollo-server-express, and all works fine. I have 2 endpoints - 1 for graphiql (/graphql) and one for non-interactive (/client) queries (I know - they were called that before I started with apollo).
app.use('/client', bodyParser.json() ,
(req, res,next) => {
const context = { pool , apiKey:req.query.key , bidules };
if (server.isAuthorized(context.apiKey)) {
return graphqlExpress({
schema: schema,
context: context,
tracing: true,
cacheControl: {
defaultMaxAge: 30,
}
}) (req,res,next);
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(403)
.send (JSON.stringify({
errors:[ {message: "api key is unauthorized"} ]
}));
}
}
);
// endpoint for browser graphIQL
app.use('/graphql', graphiqlExpress({
endpointURL: '/client'
}));
app.use("/schema", (req, res) => {
res.set("Content-Type", "text/plain");
res.send(printSchema(schema));
});
But, when I introduce apollo engine
engine.listen({
port: port,
expressApp: fidserver.app,
graphqlPaths: ['/graphql', '/client']
});
everything still works fine - except when I refresh graphiql on the browser with the query as parameters on the browser url.
Then I get this error
{"errors":[{"message":"Unsupported Content-Type from origin: text/html"}]}
Doing the same thing without apollo engine running does not cause an error. If run the query again, or refresh the browser without the query and variable parameters everything works just fine with or without Apollo Engine enabled.
When the error happens I can see from my server log that it's trying to return a react web page containing some javascript for decoding parameters from somewhere but I can't track down from where - it doesn't get as far as hitting any of my code.
This was solved by the guys at Apollo. Here's the answer - I shouldn't have had my graphIQL end point mentioned in the engine.listen options.
Engine should only be placed between the client and the GraphQL server endpoint. Try a config like this:
engine.listen({
port: port,
expressApp: fidserver.app,
graphqlPaths: ['/client'] // removed "graphql"
});

How to disable cache in apollo-link or apollo-client?

I'm using apollo-client, apollo-link and react-apollo, I want to fully disable cache, but don't know how to do it.
I read the source of apollo-cache-inmemory, it has a config argument in its constructor, but I can't build a dummy storeFactory to make it works.
You can set defaultOptions to your client like this:
const defaultOptions: DefaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore',
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all',
},
}
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
});
fetchPolicy as no-cache avoids using the cache.
See https://www.apollographql.com/docs/react/api/core/ApolloClient/#defaultoptions
Actually, setting fetchPolicy to network-only still saves the response to the cache for later use, bypassing the reading and forcing a network request.
If you really want to disable the cache, read and write, use no-cache. Which is "similar to network-only, except the query's result is not stored in the cache."
Take a look at the official docs: https://www.apollographql.com/docs/react/data/queries/#configuring-fetch-logic
I would always suggest not to disable inbuild caching feature from apollo client. Instead you can always set fetchPolicy: 'network-only' for an individual queries.
Something like this
<Query
query={GET_DOG_PHOTO}
variables={{ breed }}
fetchPolicy='network-only'
>
{({ loading, error, data, refetch, networkStatus }) => {
...
}}
</Query>
While fetching data with this Query, it would always do a network request instead of reading from cache first.

Ext Data Session and bad server response ("[W] Ignoring server record: ...")

Our application handles and manages records changes on the client side. We use ExtJS5 Data Session mechanism.
A session tracks records that need to be updated, created or destroyed
on the server. It can also order these operations to ensure that newly
created records properly reference other newly created records by
their new, server-assigned id.
Let me introduce short use case.
User opens and fills a form. Behind the scene fields are binded to entity object which is tracked by session. When user clicks Submit then session is synchronized, i.e. Ext sends requests to the server and parse response. Here I've encountered a problem.
Server returns following object but Ext does not recognize it:
[{"success": false, errorMessage: "error"}]
Ext prints warning:
[W] Ignoring server record: {"success":false}
or
[W] Ignoring server record: {"success":true}
My question is how should look server response in order to indicate that record is not accepted/saved by backend?
The source code where above warning is printed: http://docs-origin.sencha.com/extjs/5.0/apidocs/source/Operation.html (in function doProcess)
Below I put snippet how I'm starting a batch operation (sync session):
var session = this.getViewModel().getSession(),
saveBatch = session.getSaveBatch();
saveBatch.on('complete', function (batch, operation, eOpts) {
// whole batch processing has been completed
/*...*/
});
saveBatch.on('exception', function (batch, operation, eOpts) {
// exception has been occurred (possible for each operation) (such as HTTP 500)
/*...*/
});
saveBatch.on('operationcomplete', function (batch, operation, eOpts) {
// single operation has been completed
// now, every operation is marked as successful
/*...*/
});
saveBatch.start();
update 26.09.2014
Sencha developer has suggested including an id of object in the response. so I've modified server response to:
[{"id": 10, "success": false}]
but this does not solve the problem.
I spend some time on debugging Ext.data.operation.Operation.doProcess method and I analyzed a sample code from sencha support. Ultimately I've found the solution.
There is my proxy config:
proxy: {
type: 'rest',
// ...
reader: {
type: 'json',
rootProperty: 'data',
totalProperty: 'totalCount',
successProperty: 'success',
messageProperty: 'errorMessage'
}
}
Server response when some error occured:
{
"success": false,
"errorMessage": "<error message>"
}
Server response when data was successfully saved:
The minimal form for delete or update record without changing data:
{
"success": true,
}
The extended form for creating or updating record with changing record data:
{
"success": true,
"data": [{
clientId: 5, // clientIdProperty
id: 5,
// [optional] fields to change, e.g.:
name: 'new name'
}]
}
I modified a demo which I have from sencha support:
https://fiddle.sencha.com/#fiddle/bem
in proxy config use data2.json for error and data3.json for success response.

Resources