Apollo client - add new item to list following mutation - reactjs

I am working on a react app using "#apollo/client": "3.6.5". In the app a user can create payment requests that are displayed in a list.
I need to add a newly created payment request to the list of payment requests in the Apollo cache following the mutation to create one. The problem is the new payment request is not added to the cached list and Apollo does not output any error messages to explain why.
This is my version of the update function I've copied from the Apollo docs to try and update the cache.
const [onCreatePaymentRequest, createPaymentRequest] = useMutation<
CreatePaymentRequest,
CreatePaymentRequestVariables
>(CreatePaymentRequestMutation, {
update(cache, { data: { createPaymentRequest } }) {
cache.modify({
fields: {
paymentRequests(existingPaymentRequests = []) {
const newPaymentRequestRef = cache.writeFragment({
data: createPaymentRequest,
fragment: gql`
fragment NewPaymentRequest on PaymentRequest {
id
amount
status
}
`
});
return [...existingPaymentRequests, newPaymentRequestRef];
}
}
});
}
});
The mutation to create a new payment request:
export const CreatePaymentRequestMutation = gql`
mutation CreatePaymentRequest($input: CreatePaymentRequestInput!) {
createPaymentRequest(input: $input) {
paymentRequest {
id
amount
status
}
}
}
`;
This is query used to fetch payment requests:
export const GetAccountPaymentRequestsQuery = gql`
query GetAccountPaymentRequests(
$accountId: UUID!
$first: Int
$before: String
$last: Int
$after: String
) {
currentUser {
id
accountMembers(filter: { account: { id: $accountId } }) {
edges {
node {
account {
paymentRequests(
first: $first
last: $last
before: $before
after: $after
) {
edges {
node {
id
amount
status
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
}
}
}
}
`;
I think the problem may be that payment requests are nested within the schema. Using the useQuery hook I access the payment requests within a component using the following:
const paymentRequests = data.currentUser.accountMembers.edges[0].node.account.paymentRequests
.edges
I have tried numerous iterations of the update function to get this to work but so far no luck.
Thanks

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?

How to filter (remove) first x items in hygraph api (graphql)?

I am currently getting all posts with the hygraph api. But i like to filter the first x items out of that result set. All posts minus the first x items, No idea how to do it. I am using the graphql api from hygraph formerly known as graphcms. Help is appreciated, thanks.
export const getPosts = async () => {
const query = gql`
query MyQuery {
postsConnection(orderBy: created_DESC) {
edges {
node {
author {
bio
name
id
photo {
url
}
}
created
updatedAt
slug
title
excerpt
featuredImage {
url
}
localizations {
id
locale
content {
raw
}
}
categories {
name
slug
}
words
}
}
}
}
`
const results = await graphQLClient.request(query);
return results.postsConnection.edges;
};
try skip in your query param
query {
products(skip: x) {
id
}
}
https://hygraph.com/docs/api-reference/content-api/pagination#nested-pagination

React: An component attribute is not properly storing the data (from a query) that I want

I recently started learning react and I have encountered something that I do not understand. So when I declare a component I also declare an attribute in the constructor. Then, after executing the first query (I am using Apollo client - GraphQL ) I want to store the result (which I know that will be always an email) in the attribute declared so I can use it as a parameter in the second query.
The app logic is that I want to show all the orders of a given email, but first I get the email with a query.
Here is the code:
export default class Orders extends Component {
constructor(){
super();
this.email = '';
}
render() {
return (
<div>
<Query query = { GET_MAIL_QUERY }>
{({data, loading}) => {
if (loading) return "Loading...";
this.email = data.me.email;
return <h1>{this.email}</h1>
}}
At this point a header containing the email is returned, so all good. But when I execute the second query (or try to display the email in the second header for that matter) it seems that the value is not properly stored.
</Query>
<h1>{this.email}</h1>
<Query query = { GET_ORDERS_QUERY }
variables = {{
email: this.email
}}>
{({data, loading}) => {
if (loading) return "Loading...";
console.log(data);
let orders = data.ordersByEmail.data;
console.log(orders);
return orders.map(order =>
<div>
<h1>{order.date}</h1>
<h1>{order.price}</h1>
<h1>{order.conference.conferenceName}</h1>
<h1>{order.user.email}</h1>
<br></br>
</div>)
}}
</Query>
</div>
)
}
}
const GET_MAIL_QUERY = gql`
query getMyMail{
me{
email
}
}
`;
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference{
conferenceName
}
}
}
}
`;
I would love an explanation for this and maybe a solution (to store a value returned from a query to use it in another)
Thanks in anticipation :)
In my experience, you should use useQuery imported from #apollo/react-hooks with functional component because it's easy to use, it makes your code more cleaner
If your want to use <Query/> component with class component, it's ok. But, if you want to store data received from server, you should create a variable in state of constructor and when you want to update to state, you should use this.setState({email: data.me.email}). Don't use this.state.email = data.me.email, it's anti-pattern, React will not trigger re-render when you use it to update your state.
This is the code:
import React, { useState } from 'react'
import gql from 'graphql-tag'
import { useQuery, useMutation } from '#apollo/react-hooks'
const GET_MAIL_QUERY = gql`
query getMyMail {
me {
email
}
}
`
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference {
conferenceName
}
}
}
}
`
const Orders = () => {
const [email, setEmail] = useState('')
const { data: getMailQueryData, loading, error } = useQuery(GET_MAIL_QUERY, {
onCompleted: data => {
setEmail(data.me.email)
},
onError: err => alert(err),
})
const { data: getOrdersQueryData } = useQuery(GET_ORDERS_QUERY, {
variables: { email: email },
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error...</div>
return ...
}

Why does react Apollo on update subscription returns the already updated value as prev?

I'm using apollo for a react project and with subscription on the creation, update and deletion of object. I use the subscribeToMore functionality to start the subscriptions. The prev value of the updateQuery callback is the correct value for the creation and deletion subscription. But for the update subscription the prev value already contains the updated value. Overall this is really nice, as I don't need to add my custom implementation on how to update the object and can just return prev, but I don't understand why this happens. From my understanding the previous value should be returned. Is this just backed in functionality of apollo or is this some weird bug?
Here is the component which implements the subscriptions:
import { useEffect } from 'react';
import { useQuery } from '#apollo/react-hooks';
import * as Entities from './entities';
import * as Queries from './queries';
interface IAppointmentData {
appointments: Entities.IAppointment[];
error: boolean;
loading: boolean;
}
function getAppointmentsFromData(data) {
return (data && data.appointments) || [];
}
export function useAllAppointments(): IAppointmentData {
const initialResult = useQuery(Queries.GET_APPOINTMENTS);
const { data, error, loading, subscribeToMore } = initialResult;
useEffect(() => {
const unsubscribeNewAppointments = subscribeToMore({
document: Queries.NEW_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { newAppointment } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: [...prev.appointments, newAppointment],
});
},
});
const unsubscribeUpdateAppointment = subscribeToMore({
document: Queries.UPDATE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
return prev
},
});
const unsubscribeDeleteAppointments = subscribeToMore({
document: Queries.DELETE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { deleteAppointment: {
id: deletedAppointmentId
} } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: prev.appointments.filter(item => item.id !== deletedAppointmentId),
});
},
});
return function unsubscribe() {
unsubscribeNewAppointments()
unsubscribeDeleteAppointments()
unsubscribeUpdateAppointment()
}
}, [subscribeToMore]);
return {
appointments: getAppointmentsFromData(data),
error: !!error,
loading,
};
}
And these are my graphql queries / subscriptions:
import { gql } from 'apollo-boost';
export const GET_APPOINTMENTS = gql`
{
appointments {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const NEW_APPOINTMENTS_SUB = gql`
subscription newAppointment {
newAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const UPDATE_APPOINTMENTS_SUB = gql`
subscription updateAppointment {
updateAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const DELETE_APPOINTMENTS_SUB = gql`
subscription deleteAppointment {
deleteAppointment {
id
}
}
`;
According to the official Apollo documentation
Note that the updateQuery callback must return an object of the same
shape as the initial query data, otherwise the new data won't be
merged.
After playing around for a bit it seems that if the id you are returning is the same as in your cache and the object has the same shape, the update happens automatically.
But if you actually follow the CommentsPage example in the link I provided you will see that the id that is returned is new, that is why they explicity assign the object with the new data.
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
entry: {
comments: [newFeedItem, ...prev.entry.comments]
}
});
Ive tested this with my own message app Im working on, when receiving new messages with new id I have to return the merged data myself. But when im updating the chatRooms to display which chatRoom should be at the top of my screen (which one has the newest message), then my update happens automatically and I just return prev.
If however you want a work around to explicitly check the data before updating it you could try this workaround I found on GitHub. You will just need to use a reverse lookup id or just a different variable other than id.
This is what I would do in your example to achieve this:
updateAppointment {
// was id
idUpdateAppointment // im sure updateAppointmentId should also work... i think
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
}

store.getRootField(...) returns null

I call an api to add a new request:
import { commitMutation, graphql } from "react-relay";
import { REQUEST_STATUS_NEW } from "../../../constants";
const mutation = graphql`
mutation CreateRequestMutation($input: CreateRequestInput!) {
createRequest(input: $input) {
request {
id
tid
title
description
price
commission
value
expirationDate
createdAt
completionDate
multipleResponders
draft
status
requestProposals
type {
id
name
}
industry {
id
name
}
applications {
id
}
myApplication {
id
}
}
}
}
`;
let tempId = 0;
function sharedUpdater(store, request) {
const root = store.getRoot();
const newRequests = root
.getLinkedRecords("requests", { own: true })
.filter(r => r);
if (!newRequests.find(m => m.getValue("id") === request.getValue("id"))) {
newRequests.push(request);
}
root.setLinkedRecords(newRequests, "requests", { own: true });
}
export const commit = (environment, input) => {
tempId += 1;
return commitMutation(environment, {
mutation,
variables: { input },
updater: store => {
const payload = store.getRootField("createRequest");
console.log('payload: ', payload)
const request = payload.getLinkedRecord("request");
sharedUpdater(store, request);
}
});
};
But each time I call it, store.getRootField return me null. I cant really understand where is the problem where I can investigate further. Any help appreciated. Seems like server doesnt have any issues at their side. How can I debug this?

Resources