getOptimisticResponse is not working for fields with arguments - graphql

Below is my code for adding and removing a person from a group.
For some reason, getOptimisticResponse is not working for this mutation.
Could this be due to having an argument groupId for isInGroup field?
class GroupAddRemovePersonMutation extends Relay.Mutation {
static initialVariables = {
groupId: null,
}
static prepareVariables(prevVars) {
return prevVars;
}
static fragments = {
person: () => Relay.QL`
fragment on Person {
id
isInGroup(groupId: $groupId)
}
`,
}
getMutation() {
return this.props.isInGroup ?
Relay.QL`mutation { groupRemovePerson }` :
Relay.QL`mutation { groupAddPerson }`;
}
getVariables() {
const {groupId, person} = this.props;
return {
personId: person.id,
groupId,
};
}
getCollisionKey() {
const {groupId, person} = this.props;
return `groupPerson_${groupId}_${person.id}`;
}
getFatQuery() {
const {groupId, person, isInGroup} = this.props;
return isInGroup ?
Relay.QL`
fragment on GroupRemovePersonMutationPayload {
person {
id
groups { id }
isInGroup(groupId: "${groupId}")
}
group {
id
person
hasPerson(personId: "${person.id}")
}
}
` :
Relay.QL`
fragment on GroupAddPersonMutationPayload {
person {
id
groups { id }
isInGroup(groupId: "${groupId}")
}
group {
id
person
hasPerson(personId: "${person.id}")
}
}
`;
}
getConfigs() {
const {groupId, person} = this.props;
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
person: person.id,
group: groupId,
},
}];
}
getOptimisticResponse() {
const {groupId, person, isInGroup} = this.props;
return {
person: {
id: person.id,
isInGroup: !isInGroup,
},
group: {
id: groupId,
hasPerson: !isInGroup,
},
};
}
}

I would try adding the groupId to the optimistic response first. In my experience, the optimistic response has to match the shape of the fat query exactly.
If you don't have the groupIds at the time the optimistic response is generated, you could try substituting temporary values until the response is returned from the server. This scenario occurs often when you are rendering a connection and providing keys to the view to distinguish repeated React elements.

Related

WpGraphQL query returns null

I'm having this GraphQL query from headless Wordpress in Nexjs via WpGraphQl plugin:
export const GET_POSTS_BY_CATEGORY_SLUG = gql`
query GET_POSTS_BY_CATEGORY_SLUG( $slug: String, $uri: String, $perPage: Int, $offset: Int ) {
${HeaderFooter}
page: pageBy(uri: $uri) {
id
title
content
slug
uri
seo {
...SeoFragment
}
}
categories(where: {slug: $slug}) {
edges {
node {
slug
posts: posts(where: { offsetPagination: { size: $perPage, offset: $offset }}) {
edges {
node {
id
title
excerpt
slug
featuredImage {
node {
...ImageFragment
}
}
}
}
pageInfo {
offsetPagination {
total
}
}
}
}
}
}
}
${MenuFragment}
${ImageFragment}
${SeoFragment}
`;
And this is my getStaticProps function:
export async function getStaticProps(context) {
const { data: category_IDD } = await client.query({
query: GET_POSTS_BY_CATEGORY_SLUG,
});
const defaultProps = {
props: {
cat_test: JSON.parse(JSON.stringify([category_IDD])),
},
revalidate: 1,
};
return handleRedirectsAndReturnData(defaultProps, data, errors, "posts");
}
If i pass it like this in props:
const defaultProps = {
props: {
cat_test: category_IDD,
},
i get an error saying:
SerializableError: Error serializing `.cat_test` returned from `getStaticProps` in "/category/[slug]". Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
But when i JSON.parse as the code above, i get null
Whats wrong with this query?
Just noticed that the $slug is an array of strings, so here should be:
query GET_POSTS_BY_CATEGORY_SLUG( $slug: [String], $uri: String, $perPage: Int, $offset: Int )
instead of $slug: String
You're not actually passing the $slug variable to the query.
For instance if your page route is /category/[slug].js your getStaticProps should look something like this.
export async function getStaticProps(context) {
const { slug } = context.params;
const { data: category_IDD } = await client.query({
query: GET_POSTS_BY_CATEGORY_SLUG,
variables: { slug },
});
const defaultProps = {
props: {
cat_test: JSON.parse(JSON.stringify([category_IDD])),
},
revalidate: 1,
};
return handleRedirectsAndReturnData(defaultProps, data, errors, "posts");
}

Can I make my graphql query multipurpose?

I would like to query products by different filters and criteria so I have written multiple queries for my frontend for each case (shown below). Is there a way I can write and use one "multipurpose" query instead of these?
const GET_PRODUCTS = gql`
query {
products {
...productFragment
}
}
${PRODUCT_FRAGMENT}
`
const GET_PRODUCTS_BY_PRICE = gql`
query($sortFilter: String) {
products(sort: $sortFilter) {
# (sort: "price:asc") or (sort: "price:desc")
...productFragment
}
}
${PRODUCT_FRAGMENT}
`
const GET_PRODUCTS_BY_CATEGORY = gql`
query($categoryId: String) {
products(where: { categories: { id: $categoryId } }) {
...productFragment
}
}
${PRODUCT_FRAGMENT}
`
const GET_PRODUCTS_BY_CATEGORY_AND_PRICE = gql`
query($sortFilter: String, $categoryId: String) {
products(sort: $sortFilter, where: { categories: { id: $categoryId } }) {
...productFragment
}
}
${PRODUCT_FRAGMENT}
`
Looks like I can write a helper fn like this then:
function getRequiredProductsQuery({ sortFilter, categoryId }) {
if (sortFilter && categoryId) {
return { key: 'PRODUCTS_BY_CATEGORY_AND_PRICE', query: GET_PRODUCTS_BY_CATEGORY_AND_PRICE }
}
if (sortFilter) {
return { key: 'PRODUCTS_BY_PRICE', query: GET_PRODUCTS_BY_PRICE }
}
if (categoryId) {
return { key: 'PRODUCTS_BY_CATEGORY', query: GET_PRODUCTS_BY_CATEGORY }
}
return { key: 'PRODUCTS', query: GET_PRODUCTS }
}
Is it really all necessary?
ok, I figured that I can provide default params like $categoryId: String = "id:asc"

"Invalid URL: words" - apollo-server

I would like to create small project using GraphqQL, ApolloServer, but I encountered a problem, that I can't solve. I wrote it based on several documentation.
const { ApolloServer, gql } = require('apollo-server');
const { RESTDataSource } = require('apollo-datasource-rest');
const typeDefs = gql`
type Word {
id: ID!
word: String!
translation: String!
}
type Query {
words: [Word]
word(id: ID): Word
}
`;
class WordsAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'localhost:5001/'
}
async getWord(id) {
return this.get(`word/${id}`)
}
async getAllWords() {
return this.get('words')
}
async getSpecifiedWords(SpecWord) {
return this.get(`words/${SpecWord}`)
}
}
const resolvers = {
Query: {
words: (_, __, { dataSources }) =>
dataSources.wordsAPI.getAllWords(),
word: async (_source, { id }, { dataSources }) => {
return dataSources.wordsAPI.getWord(id);
}
}
}
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources: () => {
return {
wordsAPI: new WordsAPI()
};
},
context: () => {
return {
};
},
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
After
query {
words {
translation
}
}
In playground I am getting Invalid URL: words.
At localhost:5001/words is database and in Postman it works.
What did I bad?

GraphQL Subscriptions return an empty (null) response [duplicate]

I have the following GRAPHQL subscription:
Schema.graphql
type Subscription {
booking: SubscriptionData
}
type SubscriptionData {
booking: Booking!
action: String
}
And this is the resolver subsrciption file
Resolver/Subscription.js
const Subscription = {
booking: {
subscribe(parent, args, { pubsub }, info) {
return pubsub.asyncIterator("booking");
}
}
};
export default Subscription;
Then I have the following code on the Mutation in question
pubsub.publish("booking", { booking: { booking }, action: "test" });
I have the follow subscription file in front end (React)
const getAllBookings = gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`;
const getAllBookingsInitial = {
query: gql`
query {
bookings {
time
durationMin
payed
selected
activity {
name
}
}
}
`
};
class AllBookings extends Component {
state = { allBookings: [] }
componentWillMount() {
console.log('componentWillMount inside AllBookings.js')
client.query(getAllBookingsInitial).then(res => this.setState({ allBookings: res.data.bookings })).catch(err => console.log("an error occurred: ", err));
}
componentDidMount() {
console.log(this.props.getAllBookingsQuery)
this.createBookingsSubscription = this.props.getAllBookingsQuery.subscribeToMore(
{
document: gql`
subscription {
booking {
booking {
time
durationMin
payed
selected
activity {
name
}
}
action
}
}
`,
updateQuery: async (prevState, { subscriptionData }) => {
console.log('subscriptionData', subscriptionData)
const newBooking = subscriptionData.data.booking.booking;
const newState = [...this.state.allBookings, newBooking]
this.setState((prevState) => ({ allBookings: [...prevState.allBookings, newBooking] }))
this.props.setAllBookings(newState);
}
},
err => console.error(err)
);
}
render() {
return null;
}
}
export default graphql(getAllBookings, { name: "getAllBookingsQuery" })(
AllBookings
);
And I get the following response:
data: {
booking: {booking: {...} action: null}}
I get that I am probably setting up the subscription wrong somehow but I don't see the issue.
Based on your schema, the desired data returned should look like this:
{
"booking": {
"booking": {
...
},
"action": "test"
}
}
The first booking is the field on Subscription, while the second booking is the field on SubscriptionData. The object you pass to publish should have this same shape (i.e. it should always include the root-level subscription field).
pubsub.publish('booking', {
booking: {
booking,
action: 'test',
},
})

Relay: Optimistic update not causing a component rerender

I'm using PostgraphQL (https://github.com/calebmer/postgraphql) with Relay and wired a UpdateQuestionMutation into my app. However, I do not get optimistic updating to work.(When I enable network throttling in chrome I can see that the the optimistic update gets handled but the component still shows the old title).Do I miss something? I have following pieces :
class QuestionClass extends Component<IQuestion, void> {
save = (item) => {
this.props.relay.commitUpdate(
new UpdateQuestionMutation({store: this.props.store, patch: item})
);
this.isEditing = false;
};
public render(): JSX.Element {
const item = this.props.store;
console.log(item);
...
const Question = Relay.createContainer(QuestionClass, {
fragments: {
// The property name here reflects what is added to `this.props` above.
// This template string will be parsed by babel-relay-plugin.
store: () => Relay.QL`
fragment on Question {
${UpdateQuestionMutation.getFragment('store')}
title
description
userByAuthor {
${User.getFragment('store')}
}
}`,
},
});
...
export default class UpdateQuestionMutation extends Relay.Mutation<any, any> {
getMutation() {
return Relay.QL `mutation { updateQuestion }`
}
getVariables() {
console.log(this.props);
return {
id: this.props.store.id,
questionPatch: this.props.patch
}
}
getFatQuery() {
return Relay.QL `fragment on UpdateQuestionPayload { question }`
}
getConfigs() {
return [{
type: "FIELDS_CHANGE",
fieldIDs: {
question: this.props.store.id
}
}]
}
getOptimisticResponse() {
return {
store: this.props.patch
}
}
// This mutation has a hard dependency on the question's ID. We specify this
// dependency declaratively here as a GraphQL query fragment. Relay will
// use this fragment to ensure that the question's ID is available wherever
// this mutation is used.
static fragments = {
store: () => Relay.QL`
fragment on Question {
id
}
`,
};
}
Edit: That's what I see in the postgraphql logs:
mutation UpdateQuestion($input_0: UpdateQuestionInput!) { updateQuestion(input: $input_0) { clientMutationId ...F1 } } fragment F0 on Question { id rowId title description userByAuthor { id rowId username } } fragment F1 on UpdateQuestionPayload { question { id ...F0 } }

Resources