getting different data from db than in GraphiQL - reactjs

I'm new to graphQL and mongoDB and I'm trying to make it work in my project. The problem is with data from query that in GraphiQL is completely different than data from the same query inside my client side. Here's my setup of schema:
const graphql = require('graphql');
const _ = require('lodash');
const Item = require('../models/item');
const {
GraphQLObjectType,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLInt,
GraphQLList
} = graphql;
const ItemType = new GraphQLObjectType({
name: 'Item',
fields: () => ({
name: {
type: GraphQLString
},
id: {
type: GraphQLID
},
description: {
type: GraphQLString
},
price: {
type: GraphQLInt
},
image: {
type: GraphQLString
},
category: {
type: GraphQLString
}
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
item: {
type: ItemType,
args: {
id: {
type: GraphQLID
}
},
resolve(parent, args) {
// code to get data from db / other source
return Item.findById(args.id);
}
},
items: {
type: new GraphQLList(ItemType),
resolve(parent, args) {
return Item.find({})
}
}
}
});
When im doing a query from graphiQL of all the itemes and data i'm receiving is the "right one". It looks like this:
When i'm doing the same exact query from the front-end like that:
import { gql } from "apollo-boost";
const getItemsQuery = gql`
{
items {
name
id
description
price
image
category
}
}
`;
export { getItemsQuery };
The data looks like this:
It looks like it is repeating first item over and over and i can't see why. DB is also showing right items. My server side code can be found here: https://github.com/KamilStaszewski/shoppy/tree/adding_graphQL/server

From the docs:
The InMemoryCache normalizes your data before saving it to the store by splitting the result into individual objects, creating a unique identifier for each object, and storing those objects in a flattened data structure. By default, InMemoryCache will attempt to use the commonly found primary keys of id and _id for the unique identifier if they exist along with __typename on an object.
In other words, Apollo will use both __typename and id to create a cache key for each Item you fetch. This key is used to fetch the appropriate item from the cache. The problem is that your items are returning null for their id. This results in each item being written with the same key. As a result, when your query result is returned from the cache, it looks up the same key for each item in your items array.
To fix this issue, you need to ensure that your API returns a value for id. I haven't worked with mongoose that much, but I think since mongoose adds an id field for you automatically based on the _id, it should be sufficient to just remove the id from your mongoose model (not your GraphQL type). Alternatively, you could try adding a resolve function to your id field in the GraphQL type:
resolve: (item) => item._id.toString()

Related

Passing ID in GraphQL query not returning data

so i'm trying to use Apollo GraphQL with React to get specific product data by its ID, but it seems to be returning undefined. I read the Apollo docs and researched, so I'm not sure what I'm doing wrong. Also, I'm able to return data from other queries that don't require an ID (like all products, for instance). Would greatly appreciate some help!
Query
export const PRODUCT = gql`
query GetProduct($itemID: String!) {
product(id: $itemID) {
id
name
inStock
gallery
description
category
attributes {
id
name
type
items {
displayValue
value
}
}
prices {
currency {
label
symbol
}
}
brand
}
}
`;
This is where I try to return data using the ID, but to no avail:
let myID = "ps-5";
const { productLoading, productError, productData } = useQuery(PRODUCT, {
variables: { itemID: myID },
});
useEffect(() => {
if (productData) {
console.log("data: " + productData) // logs nothing. "Undefined" when if statement is removed
}
}, [])
It looks like the React client for Apollo uses the same API for useQuery as for Vue (with which I'm more familiar), in which case it should be used like this:
useQuery(PRODUCT, { itemID: myID })
(not { variables : { itemID : myID }})
I would have expected the backend to return an error though, because $itemID is declared as non-nullable.
It seems that you are destructing the object that useQuery() returns with the wrong object keys.
// instead of
const { productLoading, productError, productData } = '...'
// you can either use the regular keys as variables
const { loading, error, data } = '...'
// or assign aliases (useful when you use more queries on the same page)
// this way you can use the same variables as in your example
const { loading:productLoading, error:productError, data:productData } = '...'

how to make multipe selectable checkboxes using React,GraphQL,Apollo

I'm making multiple selectable checkboxes using React, GraphQL, Apollo.
I would like to make a query if it is selected, insert query and unselected, delete query(I want to generate a query only for selected/removed items)
Right now I'm making it using mutation but I have a problem where everything is deleted and then inserted.
I would like to generate a query only for selected/removed items. How can I fix it?
My code is as follows.
const [updateNoteMutation] = useUpdateNoteMutation();
const updateLogic = (key: string, value: string | number | Condition[]) => {
const noteVariables = {
variables: {
id: noteData.id,
noteAttributes: {
[key]: value,
},
},
};
updateNoteMutation(noteVariables).then(({ errors }) => {});
});
const handleCollection = (name: string, arr: Array) => {
//arr: List of selected checkboxes.
const noteArr = [];
diff.map((val) => {
noteArr.push({ name: val });
});
updateLogic(name, noteArr);
};
updateNoteMutation
mutation UpdateNote($id: ID!, $noteAttributes: FemappNoteInput!) {
femappUpdateNote(input: {id: $id, noteAttributes: $noteAttributes}) {
note {
id
checkboxList {
name
}
}
}
}
Please let me know if there is a source code that I can refer to
Thanks for reading my question.
You can try react-select for that. Really easy to implement and fully customizable.
Here is the repo;
https://github.com/JedWatson/react-select

How to query relational data with GraphQL, Firebase and Gatsby

I'm building a Gatsby.js site.
The site uses the gatsby-source-firestore plugin to connect to the Firestore data source.
My question is this. How can I query relational data? As in, fetch data from two models at once, where modelA[x] = modelB[y]
I don't really understand resolvers. I don't think I have any.
Note, I am not considering graph.cool currently. I'd like to stick with Firebase. I will do the relational data matching in pure JS if I have to (not GraphQL).
Here is what my gatsby-config.js looks like:
{
resolve: 'gatsby-source-firestore',
options: {
credential: require('./firebase-key.json'),
databaseURL: 'https://testblahblah.firebaseio.com',
types: [
{
type: 'Users',
collection: 'users',
map: user => ({
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
ownsHat: user.ownsHat,
hatId: user.hatId
})
},
{
type: 'Hats',
collection: 'hats',
map: hat => ({
hatType: hat.hatType,
hatUserId: hat.hatUserId,
hatId: hat.hatId
})
}
]
}
},
This pulls in two flat data models. I can query like this in-page:
any-page.js
export const query = graphql`
query {
allUsers {
edges {
node {
...UserFragment
}
}
}
}
`
What I'm looking for is a query that lets me write one query inside another i.e. a relational data query within a query.
export const query = graphql`
query {
allUsers {
edges {
node {
...UserFragment {
hats (user.userId == hat.userId){
type
hatId
}
}
}
}
}
}
`
As you can understand, this amounts to: How to run multiple GraphQL queries of relational data.
Given the nature of Firestore's flat JSON, this makes the relational aspect of GraphQL difficult.
I'm really keen to understand this better and would really appreciate being pointed down the right path.
I am really keen on sticking with GraphQL and Firebase.
Thanks!
I'm not sure this works in graphql but in Gatsby you can use gatsby-node to create and alter your nodes and inject hats to each user node. Here's an example code I'm using to add authors to a Post node:
const mapAuthorsToPostNode = (node, getNodes) => {
const author = getPostAuthorNode(node, getNodes);
if (author) node.authors___NODES = [author.id];
};
exports.sourceNodes = ({actions, getNodes, getNode}) => {
const {createNodeField} = actions;
getCollectionNodes('posts', getNodes).forEach(node => {
mapAuthorsToPostNode(node, getNodes);
});
};
This is one way to do it provided the records are not in huge numbers. If they are, you should create a hats page to display user hats where you query just the hats filtered by user id which is received via a paceContext param such as user id.
export const pageQuery = graphql`
query($userId: String) {
hats: allHats(
filter: {
userId: {eq: $userId}
}
) {
edges {
node {
...hatFragment
}
}
}
}
`;

`updater` not working with Relay Modern because `ConnectionHandler.getConnection()` returns `undefined`

I'm using Relay Modern for my app and am trying to update the cache after a mutation using the updater and optimisticUpdater but it doesn't quite work.
Basically, I have a Link type with a votes connection - here's the relevant part of my schema:
type Link implements Node {
createdAt: DateTime!
description: String!
id: ID!
postedBy(filter: UserFilter): User
url: String!
votes(filter: VoteFilter, orderBy: VoteOrderBy, skip: Int, after: String, before: String, first: Int, last: Int): VoteConnection
}
type Vote implements Node {
createdAt: DateTime!
id: ID!
link(filter: LinkFilter): Link!
updatedAt: DateTime!
user(filter: UserFilter): User!
}
# A connection to a list of items.
type VoteConnection {
# Information to aid in pagination.
pageInfo: PageInfo
# A list of edges.
edges: [VoteEdge]
# Count of filtered result set without considering pagination arguments
count: Int!
}
# An edge in a connection.
type VoteEdge {
# The item at the end of the edge.
node: Vote
# A cursor for use in pagination.
cursor: String
}
Here's the code for my Link component request the votes in a fragment:
class Link extends Component {
render() {
const userId = localStorage.getItem(GC_USER_ID)
return (
<div>
{userId && <div onClick={() => this._voteForLink()}>▲</div>}
<div>{this.props.link.description} ({this.props.link.url})</div>
<div>{this.props.link.votes.edges.length} votes | by {this.props.link.postedBy ? this.props.link.postedBy.name : 'Unknown'} {this.props.link.createdAt}</div>
</div>
)
}
_voteForLink = () => {
const userId = localStorage.getItem(GC_USER_ID)
const linkId = this.props.link.id
CreateVoteMutation(userId, linkId, this.props.viewer.id)
}
}
export default createFragmentContainer(Link, graphql`
fragment Link_viewer on Viewer {
id
}
fragment Link_link on Link {
id
description
url
createdAt
postedBy {
id
name
}
votes(last: 1000, orderBy: createdAt_DESC) #connection(key: "Link_votes", filters: []) {
edges {
node {
id
user {
id
}
}
}
}
}
`)
Finally, this is the CreateVoteMutation with the updater:
const mutation = graphql`
mutation CreateVoteMutation($input: CreateVoteInput!) {
createVote(input: $input) {
vote {
id
link {
id
}
user {
id
}
}
}
}
`
export default (userId, linkId, viewerId) => {
const variables = {
input: {
userId,
linkId,
clientMutationId: ""
},
}
commitMutation(
environment,
{
mutation,
variables,
updater: (proxyStore) => {
const createVoteField = proxyStore.getRootField('createVote')
const newVote = createVoteField.getLinkedRecord('vote')
const viewerProxy = proxyStore.get(viewerId)
const connection = ConnectionHandler.getConnection(viewerProxy, 'Link_votes')
// `connection` is undefined, so the `newVote` doesn't get inserted
if (connection) {
ConnectionHandler.insertEdgeAfter(connection, newVote)
}
},
onError: err => console.error(err),
},
)
}
The call to ConnectionHandler.getConnection(viewerProxy, 'Link_votes') only returns undefined, so the newVote doesn't actually get inserted.
Does anyone see what I'm doing wrong?
Problem:
When you're getting your connection:
const connection = ConnectionHandler.getConnection(viewerProxy, 'Link_votes')
you're trying to get the connection 'Link_votes' on the ViewerProxy. However what you want to be doing is getting the connection on the link.
Solution:
First you would need to get the id of the link that your adding the vote to.
const linkId = newVote.getLinkedRecord('link').getValue('id');
Then you want to get the Link Proxy so that you can then get the correct connection.
const linkProxy = proxyStore.get(LinkId)
Now that you have the Link Proxy that represents the link that you wanted the connection for, you can now get that connection.
const connection = ConnectionHandler.getConnection(linkProxy, 'Link_votes')
Sweet so now you've got the connection. Which solves the issue you're having.
However there is another problem, the way you go on to add the vote is wrong first you need to create an Edge out of it, and then add the edge.
First we need to create an edge
const voteEdge = createEdge(proxyStore, connection, newVote, 'VoteEdge');
Now that we have the voteEdge we can append it to the connection.
ConnectionHandler.insertEdgeAfter(connection, voteEdge).
Now it should all work. However you probably shouldn't be using the updater function for this kind of action. You should be using the RANGE_ADD configuration https://facebook.github.io/relay/docs/mutations.html#range-add and change how your server responds to that mutation.

readQuery not working with pagination in Apollo & GraphQL app

I've got the following setup for my app. I have a LinkList component that renders a list of Link components. Then I also have a CreateLink component to create new links. Both are rendered under different routes with react-router:
<Switch>
<Route exact path='/create' component={CreateLink}/>
<Route exact path='/:page' component={LinkList}/>
</Switch>
The Link type in my GraphQL schema looks as follows:
type Link implements Node {
url: String!
postedBy: User! #relation(name: "UsersLinks")
votes: [Vote!]! #relation(name: "VotesOnLink")
comments: [Comment!]! #relation(name: "CommentsOnLink")
}
I'm using Apollo Client and want to use the imperative store API to update the list after new Link was created in the CreateLink component.
await this.props.createLinkMutation({
variables: {
description,
url,
postedById
},
update: (store, { data: { createLink } }) => {
const data = store.readQuery({ query: ALL_LINKS_QUERY }) // ERROR
console.log(`data: `, data)
}
})
The problem is that store.readQuery(...) throws an error:
proxyConsole.js:56 Error: Can't find field allLinks({}) on object (ROOT_QUERY) {
"allLinks({\"first\":2,\"skip\":10})": [
{
"type": "id",
"id": "Link:cj3ucdguyvzdq0131pzvn37as",
"generated": false
}
],
"_allLinksMeta": {
"type": "id",
"id": "$ROOT_QUERY._allLinksMeta",
"generated": true
}
}.
Here is how I am fetching the list of links in my LinkList component:
export const ALL_LINKS_QUERY = gql`
query AllLinksQuery($first: Int, $skip: Int) {
allLinks(first: $first, skip: $skip) {
id
url
description
createdAt
postedBy {
id
name
}
votes {
id
}
}
_allLinksMeta {
count
}
}
`
export default graphql(ALL_LINKS_QUERY, {
name: 'allLinksQuery',
options: (ownProps) => {
const { pathname } = ownProps.location
const page = parseInt(pathname.substring(1, pathname.length))
return {
variables: {
skip: (page - 1) * LINKS_PER_PAGE,
first: LINKS_PER_PAGE
},
fetchPolicy: 'network-only'
}
}
}) (LinkList)
I am guessing that the issue somehow has to do with my pagination approach, but I still don't know how to fix it. Can someone point me into the right direction here?
How to read a paginated list from the store depends on how you do the pagination. If you're using fetchMore, then all the data will be stored under the original keys of the query, which in this case I guess was fetched with { first: 2, skip: 0 }. That means in order to read the updated list from the store, you would have to use the same parameters, using { first: 2, skip: 0 } as variables.
PS: The reason Apollo does it this way is because it still allows you to relatively easily update a list via a mutation or update store. If each page was stored separately, it would be very complicated to insert an item in the middle or the beginning of the list, because all of the pages would potentially have to be shifted.
That said, we might introduce a new client-side directive called #connection(name: "ABC") which would let you explicitly specify under which key the connection is to be stored, instead of automatically storing it under the original variables. Happy to talk more about it if you want to open an issue on Apollo Client.

Resources