Passing Arguments to GraphQL with Next.JS and Apollo - reactjs

A lot of this is very new to me since I have been a PHP developer for so long.
I am having a couple of issues that I know are easy to fix. I just have limited experience and am learning.
I cannot seem to pass the argument $token_id. I am using next JS, and I just want to take the number on the [id].js page, and pass it as the token ID.
I can't seem to get data from traitType, which is an object named "traits" which has an array of objects inside of it.
export async function getStaticProps(context) {
const client = new ApolloClient({
uri: 'http://localhost:3000/api/graphql',
cache: new InMemoryCache()
});
const token_id = String(context.params.id)
const { data } = await client.query({
query: gql`
query myQuery($token_id: String!) {
getData(token_id: $token_id) {
token_id
name
}
}
`
},{ variables: { token_id } });
props: {
data: data,
}
}
export async function getStaticPaths() {
return {
paths: [], //indicates that no page needs be created at build time
fallback: 'blocking' //indicates the type of fallback
}
}
My Type Definition
type Blah {
token_id: String
name: String
#traits: [traitType] #TODO: returns nothing since data is array
}
type traitType {
trait_type: String
value: String
}
I have to hard code the token
getData(token_id: "6") {
token_id
name
traits {
trait_type
}
}
I have tried several variations in the graphql query
query getDataQuery($token_id: String! = "6") {
getData(token_id: $token_id) {
Returns Null on traits, and $token id is null, and nothing is found
"traits": [
{
"trait_type": null,
"value": null
}
],
And my JSON from mongo db is an array with an object.
"traits" : [
{
"trait_type" : "birthday",
"value" : 1627423029.0,
}
]
I can post more than just fragments of code. Sorry for the formatting.

Related

Relay-style pagination with Apollo Client

I am trying to paginate 2 relay-style fields posts_connection and comments_connection
For instance, the query is extremely simple,
const QUERY = gql `
PostsQuery($comments_first: Int, commments_after: String) {
posts_connection {
edges {
node {
...INFO_ABOUT_A_POST
comments_connection(first:$comments_first, after:$commments_after) {
edges {
node {
...INFO_ABOUT_A_COMMENT
}
}
}
}
}
}
}
`
To do this with Apollo client, we configure the cache using relayStylePagination(), as in https://www.apollographql.com/docs/react/pagination/cursor-based/#relay-style-cursor-pagination
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts_connection: relayStylePagination(),
},
},
posts: { // posts is the type of a single post node
fields: {
keyFields: ["postid"],
comments_connection: relayStylePagination()
}
},
comments: {
keyFields: ["commentid"],
},
}
}
My process is
Run the initial query,
data has a single initial comment, COMMENT1
fetchMore comments
fetchMore({variables : {comments_after:PREVIOUS_END_CURSOR} })
We fetch a new comment COMMENT2
The issue: data should contain [ COMMENT1, COMMENT2]
Instead, the new comment overwrites the old one, and data only contains COMMENT2
Why is this happening and how do I solve it?

Sitecore GraphQL from the frontend

I am looking for an article/example taht can help me do the following:
Run a graphql query (which runs perfectly in the Playground) through my react code
This is the code I am referring to:
export const getStaticProps : GetStaticProps = async(context)=> {
const endPoint = process.env.PREVIEW_URL as string;
const graphQLClient = new GraphQLClient(endPoint);
graphQLClient.setHeader('X-GQL-Token', process.env.PREVIEW_API as string);
const query = gql`{
item(path: "/sitecore/content/sug-containers/home/Item" language: "en"){
field(name: "imageSrc") {
... on ImageField {
id(format: "B")
name
src
}
}
}
}
}`
const data = await graphQLClient.request(query);
console.log(JSON.stringify(data, undefined, 2))
return{
props:{data}
};
}
However, no data is every returned. Props is always null.
I think my query definition is incorrect. I copied the query that runs successfully in the playground. Does anyone know what the problem could be or point me to a working example somewhere please? I have referred to this article:
https://thetombomb.com/posts/do-you-need-graphql-client
In the GraphQL playground, this is the query I use:
query {
item(path: "/sitecore/content/sug-containers/home/AvistaLogo" language: "en"){
field(name: "imageSrc") {
... on ImageField {
id(format: "B")
name
src
}
}
}
}
And here is the output:
{
"data": {
"item": {
"field": {
"id": "{94B0DA6A-D944-489B-B6B0-39094114F244}",
"name": "imageSrc",
"src": "/sitecore/shell/-/media/Images/Logos/logo.svg"
}
}
}
}

React Apollo Client - query results mixing up in cache

I use apollo client to fetch book graphs and use relay style pagination. Both of the following NEW_BOOKS query and ALL_BOOKS query works fine independently.
Currently, I am using NEW_BOOKS in the home page and ALL_BOOKS in a popup in the home page.
When the Homepage is opened NEW_BOOKS gets loaded fine.
When the popup is opened and ALL_BOOKS is fetched, newBooks become undefined or the result of ALL_BOOKS query.
Why is this happening?
const { loading, data: newBooks, fetchMore, networkStatus } = useQuery(NEW_BOOKS, {
variables: {
first: PAGE_SIZE,
after: endCursor
},
notifyOnNetworkStatusChange: true
});
const NEW_BOOKS = gql`query GetNewBooks($first:Int!, $after:String){
books(
first: $first, after: $after,
filters: [
{
path: "isNew",
value: true
}
]
) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
name
author {
id
name
}
}
}
}
}`;
-All books query filterable by name
const { loading, data: filteredBooks, fetchMore, networkStatus } = useQuery(ALL_BOOKS, {
variables: {
first: PAGE_SIZE,
after: endCursor,
name: nameFilter
},
notifyOnNetworkStatusChange: true
});
const ALL_BOOKS = gql`query GetAllBooks($first:Int!, $after:String, $name:String){
books(
first: $first, after: $after,
filters: [
{
path: "name",
value: $name,
type: "contains"
}
]
) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
name
copiesSold
author {
id
name
}
}
}
}
}`;
The cache being used looks like this,
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: relayStylePagination(),
},
}
},
});
We have to pass keyArgs explictly when relayStylePagination or similar pagination is used.
A keyArgs: ["type"] field policy configuration means type is the only argument the cache should consider (in addition to the field name and the identity of the enclosing object) when accessing values for this field. A keyArgs: false configuration disables the whole system of differentiating field values by arguments, so the field's value will be identified only by the field's name (within some StoreObject), without any serialized arguments appended to it.
KeyArgs documentation here.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: relayStylePagination(["name"]),
},
}
},
});

Handling Graphql Mutation update, cache read and writeQuery, if the query is dynamic?

Doing nightlife app on freecodecamp https://learn.freecodecamp.org/coding-interview-prep/take-home-projects/build-a-nightlife-coordination-app/
I am trying to implement 'Go' button, similarly 'Like' button on Youtube or Instagram. Users click the button the number(counting how many users go) goes up meaning users will go there and click again, it revokes, the number decreases, users will not go there.
It seems like working well except the issue, I have to refresh the page and then, the number has increased or decreased and throws the error like below so:
Invariant Violation: Can't find field getBars({}) on object {
"getBars({\"location\":\"vancouver\"})": [
{
"type": "id",
"generated": false,
"id": "Bar:uNgTjA9ADe_6LWby20Af8g",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:CwL5jwXhImT_7K5IB7mOvA",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:mdt1tLbkZcOS2CsEbVF9Xg",
"typename": "Bar"
},
.
.
.
I am assuming handling update function will fix this issue but unlike the example from Apollo documentation:
// GET_TODOS is not dynamic query
// nothing to pass as variables to fetch TODO list
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
My query is dynamic:
// I have to pass location variable, otherwise it won't fetch anything.
const GET_BARS_QUERY = gql`
query getBars($location: String!) {
getBars(location: $location) {
id
name
url
rating
price
image_url
goings {
username
}
goingCount
}
}
`;
I believe I might need to handle to provide location using readQuery and writeQury but not too sure what I should do.
Here's my code:
const GoButton = ({ user, bar }) => {
const { token } = user;
const { id, goings, goingCount } = bar;
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data });
}
});
return (
<Button onClick={userGoes}>
Go {goingCount}
</Button>
);
};
const GO_MUTATION = gql`
mutation go($yelp_id: String!) {
go(yelp_id: $yelp_id) {
id
goings {
id
username
}
goingCount
}
}
`;
export default GoButton;
Full code here https://github.com/footlessbird/Nightlife-Coordination-App
when you read/write the getBars query, you need to pass the location as a variable
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY,
variables: {
location: 'New York'
}
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data,
variables: {
location: 'New York'
}
});
}
});

Fragment cannot be spread here as objects of type "X" can never be of type "Y"

In this case, type "X" is Application and type "Y" is type "Node" - I can see why this is happening, but my understanding of Relay isn't enough to understand how to fix it. The query generated by Relay is
query {
node(id: $some_id) {
...F0
}
}
fragment F0 on Application {
...
}
I have a schema that looks like
query {
application {
/* kind of a generic endpoint for fetching lists, etc */
invites(token: $token) {
name
}
}
viewer { /* the current user */ }
}
I'm trying to fetch a specific invite from outside a session (viewer is null).
I've tried
const application = Relay.QL`query { application }`
...
<Route ... queries={{ application }}/>
...
Relay.createContainer(Component, {
initialValues: { token: null },
fragments: {
application: () => {
fragment on Application {
invites(token: $token) {
...
}
}
}
}
})
which gives me the error
fragment "F0" cannot be spread here as objects of type "Node" can never be of type "Application" - or something to that effect.
I'm a little confused, because if I were to write a raw query and run it through GraphQL directly
query {
application {
invites(token: "asdasdasd") {
edges {
node {
name
}
}
}
}
}
it gives me what I'm looking for...
In the backend, my graph is defined like
export const Application = new GraphQLObjectType({
name: 'Application',
fields: () => ({
id: {
type: GraphQLString,
resolve: () => 'APPLICATION_ID'
},
invites: {
type: InviteConnectionType,
args: connectionArgs,
resolve: (application, args) => {
...
}
}
})
})
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'query',
fields: {
node: nodeField,
application: {
type: Application,
resolve: (root, args, ctx) => {
return Promise.resolve({})
}
}
}
})
I've been looking at questions like this and some issues on the Relay github, but it's not clear to me how I should implement nodeInterface.
edit: the short-long of the current nodeInterface code is
export const {
nodeInterface,
nodeField
} = nodeDefinitions(
(globalId) => {
const { type, id } = fromGlobalId(globalId)
return db[type].findById(id)
},
(obj) => {
const name = obj.$modelOptions.name.singular
return types[name]
}
)
Application is not a db model, however, just a generic interface to fetch data through. I've tried checking to see if type === 'Application', and returning null (although I see why that doesn't work), returning Application (the GraphQLObject), but that doesn't work... not really sure where to go from there.
You need to automatically generate an unique global id for a GraphQL
type that you want to refetch.
In nodeInterface you tell GraphQL
how to map the id to the corresponding GraphQL object.
By the given server-side object nodeInterface identifies the GraphQL type.
Below is simplified example how it may look like with Application:
// nodeInterface.
var {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
var {type, id} = fromGlobalId(globalId);
// The mapping from globalId to actual object id and type.
console.log('globalId:', id);
console.log('type:', type);
if (type === 'Application') {
// getApplication is your db method to retrieve Application object.
// With id you could also retrieve a specific db object.
return getApplication();
} else {
return null;
}
},
(obj) => {
// Note that instanceof does an identity check on the prototype object, so it can be easily fooled.
if (obj instanceof Application) {
return ApplicationType;
} else {
return null;
}
},
);
// Application.
export const ApplicationType = new GraphQLObjectType({
name: 'Application',
fields: () => ({
// Auto-generated, globally defined id.
id: globalIdField('Application'),
_id: {
type: GraphQLString,
resolve: () => 'APPLICATION_ID'
},
invites: {
type: InviteConnectionType,
args: connectionArgs,
resolve: (application, args) => {
...
}
}
}),
// Declaring nodeInterface.
interfaces: [nodeInterface]
});
Note that during the initial fetch nodeInterface is not even executed, so if nodeInterface is returning nothing there won’t be errors at the initial fetch. If that doesn’t make sense or you’re still struggling you can post a link to the repo, I’ll look into it.
To give an update on this, I was on the right path.
The current nodeDefinitions I had just needed a little extra:
nodeDefinitions(
(globalId) => {
const { type, id } = fromGlobalId(globalId)
if (type === 'Application') {
return Promise.resolve(Application)
}
return db[type].findById(id)
},
(obj) => {
if (obj.$modelOptions) {
/* sequelize object */
const name = obj.$modelOptions.name.singular
return types[name]
} else if (obj.name === 'Application') {
return Application
}
return null
}
)
I'm not sure if this is the best way to do it, but it seems to do the trick. The gist is that, if the type of node that I want to be returned is Application, I return the GraphQL object - { ... name: "Application" ... } we'll use the name field from this in the next step (the second callback in nodeDefinitions) to just re-return Application. I think you could return a "custom" object or something else - it doesn't matter so long as you return something unique that you can define a mapping from to a GraphQLObject type (required for the second callback).

Resources