dataIdFromObject overwriting cache from other queries incorrectly - apollo

So using dataIdFromObject as the following:
cache: new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'AppKey':
return object.appKeyId
case 'App':
return object.appId
default:
return defaultDataIdFromObject(object)
}
},
})
is some how rewriting the first object.name in appKey from app, or sometimes vice versa. For example
data.getAppKeys = [{ appKeyId: 1, name: 'My App' }, ...correctObjects] when the backend has the key as {appKeyId: 1, name: 'myAppKey'}. This doesn't occur when commenting out either of the cases from dataIdFromObject.
How can I get the cache to rewrite the correct queries?

You need to have a unique identifier that goes beyond a number, because the numbers will match and the cache will overwrite incorrectly, the solution was is add the __typename into the data identifier like so:
dataIdFromObject: object => {
const getID = (typename, id) => `${typename}_${id}`
switch (object.__typename) {
case 'AppKey':
return getID(object.__typename, object.appKeyId)
case 'App':
return getID(object.__typename, object.appId)
default:
return getId(object.__typename, defaultDataIdFromObject(object))
}
},
})

Related

How to update an item after being newly created in AWS DynamoDB and Amplify

I am trying to update a query in AWS Dynamo using AWS Amplify on top of Next.js.
My scenario is simple.
On page load, if there exists a user and the user has not visited a page before, a new object will be created with set values using SWR.
const fetchUserSite = async (owner, code) => {
try {
// Create site object if no site exists
if (userData == null) {
const siteInfo = {
id: uuidv4(),
code: parkCode,
owner: user?.username,
bookmarked: false,
visited: false,
}
await API.graphql({
query: createSite,
variables: {input: siteInfo},
authMode: 'AMAZON_COGNITO_USER_POOLS',
})
console.log(`${code} added for the first time`)
}
return userData || null
} catch (err) {
console.log('Site not added by user', data, err)
}
}
// Only call the fetchUserSite method if `user` exists
const {data} = useSWR(user ? [user?.username, parkCode] : null, fetchUserSite)
Currently, this works. The object is added to the database with the above attributes. HOWEVER, when I click a button to update this newly created object, I get an error of path: null, locations: (1) […], message: "Variable 'input' has coerced Null value for NonNull type 'ID!'"
This is my call to update the object when I click a button with the onClick handler "handleDBQuery".
const handleDBQuery = async () => {
await API.graphql({
query: updateSite,
variables: {
input: {
id: data?.id,
bookmarked: true,
owner: user?.username,
},
},
authMode: 'AMAZON_COGNITO_USER_POOLS',
})
console.log(`${name} Bookmarked`)
}
My hunch is that the updateSite query does not know about the createSite query on page load.
In short, how can I update an item after I just created it?
I looked into the code at master branch and follow along as you describe. I found that the data?.id here comes from a state variable and it is set only before the call to createSite. I suggest you try setId again using the data returned from the createSite
Try this
const fetchUserSite = async (owner, code) => {
try {
// Create site object if no site exists
if (userData == null) {
const siteInfo = {
id: uuidv4(),
code: parkCode,
owner: user?.username,
bookmarked: false,
visited: false,
}
const { data: newData } = await API.graphql({
query: createSite,
variables: {input: siteInfo},
authMode: 'AMAZON_COGNITO_USER_POOLS',
});
setId(newData.id); // <====== here (or setId(siteInfo.id))
console.log(`${code} added for the first time`)
return newData; // <======= and this, maybe? (you may have to modify the qraphql query to make it return the same item as in the listSite
}
return userData || null
} catch (err) {
console.log('Site not added by user', data, err)
}
}

getted data is only null in apollo-client / apollo-server & useSubscription

I try use pubsub in apollo server & apollo client. but subscribed data is only null.
client dependency
"#apollo/react-hooks": "^3.1.5",
"apollo-boost": "^0.4.9",
"apollo-link-ws": "^1.0.20",
"graphql": "^15.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"styled-components": "^5.1.1",
"subscriptions-transport-ws": "^0.9.16",
"typescript": "~3.7.2"
server dependency
"apollo-server": "^2.14.1",
"graphql": "^15.0.0",
"merge-graphql-schemas": "^1.7.8",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
"typescript": "^3.9.3"
// apolloClient.ts
import { ApolloClient, HttpLink, InMemoryCache, split } from 'apollo-boost'
import { WebSocketLink } from 'apollo-link-ws'
import { getMainDefinition } from 'apollo-utilities'
const wsLink = new WebSocketLink({
uri: 'ws://localhost:4000/graphql',
options: {
reconnect: true
}
})
const httpLink = new HttpLink({
uri: 'http://localhost:4000'
})
const link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
)
const cache = new InMemoryCache()
const client = new ApolloClient({
cache: cache,
link: link,
})
export default client
// subscribe.ts
const ON_PUT_UNIT = gql`
subscription onPutUnit($code: String!) {
onPutUnit(code: $code)
}
`
const onPutResult = useSubscription(
ON_PUT_UNIT,
{ variables: {
code: code,
}}
)
// in is only null!!
console.log('subscribe', onPutResult)
-server-
onPutUnit.ts
type Subscription {
onPutUnit(code: String!): Room
}
import { pubsub } from '#src/index'
const { withFilter } = require('apollo-server')
export default {
Subscription: {
onPutUnit: {
subscribe: withFilter(
() => pubsub.asyncIterator(['PUT_UNIT']),
(payload: any, variables: any) => {
// no problem in payload & variable data
return payload.code === variables.code
}
)
}
},
}
putUnit.ts
type Mutation {
putUnit(code: String!, x: Int!, y: Int!, userName: String!): Room!
}
export default {
Mutation: {
putUnit: async (_: any, args: args) => {
const { code, x, y, userName } = args
const room = findRoom(code)
console.log(room) // no problem. normal data.
pubsub.publish('PUT_UNIT', room)
return room
},
},
}
Is it some problem? subscribe event is normally reached to client when publish. but data is is only null. I can't fine the reason.
You only specified a subscribe function for onPutUnit, without specifying a resolve function. That means the field utilizes the default resolver.
The default resolver just looks for a property with the same name as the field on the parent object (the first parameter passed to the resolver) and returns that. If there is no property on the parent object with the same name as the field, then the field resolves to null. The parent object is the value the parent field resolved to. For example, if we have a query like this:
{
user {
name
}
}
whatever the resolver for user returns will be the parent value provided to the resolver for name (if user returns a Promise, it's whatever the Promise resolved to).
But what about user? It has no parent field because it's a root field. In this case, user is passed the rootValue you set when initializing the ApolloServer (or {} if you didn't).
With subscriptions, this works a bit differently because whatever value you publish is actually passed to the resolver as the root value. That means you can take advantage of the default resolver by publishing an object with a property that matches the field name:
pubsub.publish('PUT_UNIT', { onPutUnit: ... })
if you don't do that, though, you'll need to provide a resolve function that transforms the payload you published. For example, if we do:
pubsub.publish('PUT_UNIT', 'FOOBAR')
Then our resolver map needs to look something like this:
const resolvers = {
Subscription: {
onPutUnit: {
subscribe: ...,
resolve: (root) => {
console.log(root) // 'FOOBAR'
// return whatever you want onPutUnit to resolve to
}
}
},
}

Invariant Violation error when updating apollo cache after mutation

I try update my list after item remove by this article
but get Invariant Violation error.
my mutation:
const deleteFn = useMutation<FeaturedPlaylistGroupDelete, FeaturedPlaylistGroupDeleteVariables>(deleteQuery, {
update: (cache, mutationResult) => {
console.log('mutationResult', mutationResult)
const data = cache.readQuery({ query: featuredPlaylistsGroupsQuery })
console.log('cache', cache)
console.log('cacheData', data)
cache.writeQuery({
query: featuredPlaylistsGroupsQuery,
data: data.filter((item) => item.id !== mutationResult.data.featuredPlaylistGroupDelete.id),
})
},
})
featuredPlaylistsGroupsQuery:
export const featuredPlaylistsGroupsQuery = gql`
query FeaturedPlaylistGroups(
$active: Boolean
$noCategory: Boolean
$dateFrom: String
$dateTo: String
$title: String
$regions: [String!]
$categories: [String!]
) {
featuredPlaylistGroups(
active: $active
noCategory: $noCategory
dateFrom: $dateFrom
dateTo: $dateTo
regions: $regions
title: $title
categories: $categories
) {
active
category {
title
}
datetime
id
region
title
}
}
`
deleteQuery:
const deleteQuery = gql`
mutation FeaturedPlaylistGroupDelete($id: String!) {
featuredPlaylistGroupDelete(id: $id) {
active
categoryId
category {
title
}
datetime
id
region
title
}
}
`
error:
Invariant Violation: Can't find field
featuredPlaylistGroups({}) on object {
...
When you use readQuery, what's returned is what would have been returned in the data part of the response for that query. This is always an object. So for a query like
query {
foo
bar
}
You get an object like
{
"foo": "FOO",
"bar": "BAR"
}
When you call readQuery using your featuredPlaylistsGroupsQuery, you'll get an object with a single property named featuredPlaylistGroups. So your code should look more like:
const cached = cache.readQuery({ query: featuredPlaylistsGroupsQuery })
const featuredPlaylistGroups = cached.featuredPlaylistGroups.filter(item => {
return item.id !== mutationResult.data.featuredPlaylistGroupDelete.id
})
const data = {
...cached,
featuredPlaylistGroups,
}
cache.writeQuery({
query: featuredPlaylistsGroupsQuery,
data: data,
})
However, this still will not work because featuredPlaylistsGroupsQuery takes a number of variables. We need those variables in order to read and write from the cache, since each combination of variable that has been queries is stored separately in the cache. So you will either need to keep track of the variables used and call readQuery/writeQuery on all used combinations, or use something like apollo-link-watched-mutation

How do you find an item in a repository by something other than id

If I have a repository with many properties and I want to find something by the non-id property, do I just find all and then return the data after a boolean comparison, or is there a better way to find by a property that's not the ID?
In loopback4, you need to use repository for this purpose. Do as below.
For case where you know there will be just one entry with value. (Unique columns)
const user = await this.userRepository.findOne({
where: {
username: 'test_admin'
}
});
For case where there can be multiple.
const user = await this.userRepository.find({
where: {
firstName: 'test admin'
}
});
For Loopback 3, here you find the documentation for querying data: https://loopback.io/doc/en/lb3/Querying-data.html
Basically, use a query filter like this:
const objects = await app.models.ModelName.find(
{
where: {
propertyName: value
}
}
)
Don't forget to define an index for the property you want to query because otherwise, the database engine will perform a full table scan.
"properties": {
"propertyName": {
"type": "string",
"index": {
"unique": true
}
},
...
}

Ember - Within action, result is defined, returnvalue of same action logged in parent action is undefined? Why?

Quick and shortly I have following problem:
I have following two actions within a component in Ember:
createData: function(user) {
let collection = [];
for (let i = 0; i < user.posts.length; i++) {
let data = this.send('createSingleData',user.posts[i], user, 'post');
console.log(data);
collection.push(data);
}
return collection;
},
createSingleData: function(data, user, type) {
let entitySkeleton = {
name: data.place.name,
belongsTo: user.id,
position: {
data.place.location.longitude,
data.place.location.latitude
}
};
console.log(entitySkeleton);
return entitySkeleton;
}
the first log - within createSingleData, right before returning the logged value - writes the entitySkeleton as Object into the console - as expected.
However, the console.log(data) - within createData - writes 'undefined' to the console.
Is there any aspect of asynchrounosity I didn't respect?
P.S.:
I also logged any paramater within createSingleData, they are all set properly.
The variable collection also only gets pushed 'undefined'.
You cannot return the value from action, instead you can set property from the action.
how to return values from actions in emberjs
actions: {
PrintSomething: function() {
let obj = [{a: 'raj'}, {a: 'Prudvi'}, {a : 'thimappa'}]
console.log('before', obj);
this.send('returnSomething', obj);
console.log('after calling action', this.get('returnvalue'));
},
returnSomething: function(obj) {
obj.push({a: 'FSDFSDF'})
var data = obj;
this.set('returnvalue', data);
}
}