Relay Store Updater Not Working - reactjs

Im trying to do an updater to a mutation and its not working well it says to me that the 'setValue is not a function', and when do a console.log on the newEvent and on relayEvent it returns to me the right data can somebody help me please!
My mutation its workin but somehow the data is not being updated, so i needed to do a updater that is not working
Heres my code:
/* #flow */
import { graphql, commitMutation } from "react-relay";
import environment from "../../../relay/environment";
import type { EventSetAttendedInput } from "./__generated__/EventSetAttendedMutation.graphql";
import { connectionUpdater } from "../../../relay/mutationUtils";
const mutation = graphql`
mutation EventSetAttendedMutation($input: EventSetAttendedInput!) {
EventSetAttended(input: $input) {
event {
id
_id
attended(first: 10000) {
__typename
edges {
node {
person {
name
_id
id
}
}
}
}
invitations(first: 10000) {
__typename
edges {
node {
attended
person {
name
_id
id
}
}
}
}
}
error
}
}
`;
let tempID = 0;
const commit = (input: EventSetAttendedInput, onCompleted, onError) => {
return commitMutation(environment, {
mutation,
variables: {
input: {
...input,
clientMutationId: tempID++
}
},
onCompleted,
onError,
updater: store => {
let createAttendedField = store.getRootField("EventSetAttended");
let newEvent = createAttendedField.getLinkedRecord("event");
const relayEvent = store.get(input.eventId);
console.log(`eventStore: `, newEvent);
console.log(`relayEvent: `, relayEvent);
store.setValue(newEvent, "event");
}
});
};
export default { commit };

updater: (store, data) => {
let createAttendedField = store.getRootField("EventSetAttended");
let newEvent = createAttendedField.getLinkedRecord("event");
const relayEvent = store.get(input.eventId);
relayEvent.setLinkedRecord(newEvent, "event");
}

Related

Typescript shaping: What is the meaning of each section of this Next.js component?

Let's take this function, for example:
interface IParams extends ParsedUrlQuery {
id: string
}
interface Props {
params: {
id: string;
};
}
export const getStaticProps: GetStaticProps<IParams> = async ({ params: { id } }: Props) => {
const { data: lesson } = await supabase
.from("lesson")
.select("*")
.eq("id", id)
.single();
return {
props: {
lesson,
},
};
};
When we review this signature:
export const getStaticProps: GetStaticProps<IParams> = async ({ params: { id } }: Props) => { ... }
What is the first <IParams> shape defining? Is this the input of the getStaticProps function, or its output?
If I have <IParams> specified, do I need the "Props" shape?
What defines the shape of the output using this example?
Let's start with GetStaticProps<FirstShape, SecondShape> and the dynamic page [id].js.
The first shape is the return type of getStaticProps, which should also match the prop type for the page.
The second shape is the query params for getStaticProps.
Since the query param is id (defined by the file name) will be passed in query params. However, it could also be undefined, so you must account for that.
[id].js
import { GetStaticProps } from "next";
import { FC } from "react";
type PageProps = {
yourData: any;
};
type QueryParams = {
id: string;
};
const Page: FC<PageProps> = ({ yourData }) => {
console.log(yourData);
return <div>Page</div>;
};
export const getStaticProps: GetStaticProps<PageProps, QueryParams> = ({ params }) => {
if(!params || params.id) {
return { notFound: true };
}
return {
props: {
yourData: { id: params.id },
},
};
};
export default Page;
Based on your example, you'd have something like...
import { GetStaticProps } from "next";
import { FC } from "react";
type PageProps = {
lesson: any // your lesson type
};
type QueryParams = {
id: string;
};
const Page: FC<PageProps> = ({ lesson }) => {
console.log(lesson);
return <div>Page</div>;
};
export const getStaticProps: GetStaticProps<PageProps, QueryParams> = async ({
params, // possibly undefined, don't destructure here
}) => {
try {
if (!params || params.id) throw Error("No id"); // throw error, so it goes to the catch statement to return notFound
const { id } = params; // destructure after you know params is defined
const { data, error } = await supabase.from("lesson").select("*").eq("id", id).single();
if (!data || error) throw Error(error.message ?? "No data");
return { props: { lesson: data } };
} catch (e) {
console.error(e);
return { notFound: true };
}
};
export default Page;

Run sequental graphql queries using Apollo Client

I am trying to load data using below queries in React.
export const GET_POSTS = gql`
query GetLaunches($limit: Int) {
launches(limit: $limit) {
name
id
date_utc
}
}
`;
I am using above query like below inside React component.
const {loading, error, data} = useQuery(GET_POSTS, {
variables: { limit },
skip: !limit
});
Above return data like below.
data = {
items: [
{
date_utc: "2014-01-06T18:06:00.000Z"
id: ['9D1B7E0']
name: "Thaicom 6"
},
{
date_utc: "2014-01-06T18:06:00.000Z"
id: ['1S1B5D0']
name: "Thai 0"
},
{
date_utc: "2014-01-06T18:06:00.000Z"
id: ['7F1B7E3']
name: "Sun 6"
},
{
date_utc: "2014-01-06T18:06:00.000Z"
id: ['4A1B5K7']
name: "Moon 0"
}
]
}
Now what I want to make another query where I will pass above id as a variable to another query and load description of each individual above list of items.
I tried below. Below is queries.ts file.
import { gql } from 'apollo-boost';
export const GET_POSTS = gql`
query GetLaunches($limit: Int) {
launches(limit: $limit) {
name
id
date_utc
}
}
`;
export const GET_DESCRIPTION = gql`
query GetDescription($id: ID!) {
mission(id: $id) {
description
}
}
`;
Here is Home.tsx file.
import React, { useEffect, useState } from 'react';
import { useQuery } from '#apollo/react-hooks';
import { HomeContainer } from '../Container/';
import { GET_POSTS, GET_DESCRIPTION } from '../Queries/';
export const Home: React.FC = () => {
const limit = 10;
const launches = useQuery(GET_LAUNCHES, {
variables: { limit },
skip: !limit
});
const launchDescription = useQuery(GET_DESCRIPTION, {
variables: { id: launches?.data?.launches?.mission_id?.[0] },
skip: launches.loading || !launches?.data?.launches?.mission_id?.[0],
});
useEffect(() => {
console.log(launchDescription.data); // returns undefined
console.log(launches.data); // this returns correct data
}, [launches]);
return (
<HomeContainer
loading={launches.loading}
error={launches.error}
data={launches.data}
/>
);
}
Inside useEffect hook, I want to add description to each individual launch in launches.data array. How can I make this work?
You need to use optional chaining for launches?.loading or simply just omit this to !launches?.data?.launches?.mission_id?.[0]
Both of your queries right now are fired at the same time. What you need to do is make the second query wait for the first one to be completed (using useLazyQuery), and only fire when the ID is available.
Here is how that should look:
import React, { useEffect, useState } from 'react';
import { useQuery, useLazyQuery } from '#apollo/react-hooks';
import { HomeContainer } from '../Container/';
import { GET_POSTS, GET_DESCRIPTION } from '../Queries/';
export const Home: React.FC = () => {
const limit = 10;
const launches = useQuery(GET_POSTS, {
variables: { limit },
skip: !limit
});
// useLazyQuery provides a function you can use to fire the query
// only when you have what you need for it
const [getLaunchDescription, launchDescription] = useLazyQuery(GET_DESCRIPTION)
useEffect(() => {
// check that you have the id before you ask for the description
if (launches?.data?.launches?.mission_id?.length) {
getLaunchDescription({
variables: { id: launches.data.launches.mission_id[0] }
});
}
}, [launches])
return (
<HomeContainer
loading={launches.loading}
error={launches.error}
data={launches.data}
/>
);
}

how can I pass arguments to the query method in apollo?

I am using apollo client to fetch the data.
And I want it to only get those todos that are made by the logged in user only.
But this piece doesn't work
Source code:
import { gql } from '#apollo/client';
export const todoService = {
getTodoItems: () => gql`
query todoQuery($loggedUserId: String!) {
todo(where: { userId: { _eq: $loggedUserId } }, order_by: { createdAt: desc }) {
id
todo {
title,
body,
status
}
userId
}
}
}
`
Redux thunk file
import { createAsyncThunk } from '#reduxjs/toolkit';
import { apolloClient } from '#/Apollo';
import { todoService } from './_graphql';
export const todoThunk = {
getTodoItems: createAsyncThunk(`db/getTodoItems`, async (loggedUserId: string) => {
const response = await apolloClient.query({
query: todoService.getTodoItems(),
variables: { loggedUserId },
fetchPolicy: `network-only`,
});
return response;
}),
React Component
useEffect(
dispatch(todoThunk.getTodoItems(loggedUserId));
,[dispatch])
However it works when I hard code the userId in place of variable loggedUserId like this:
export const todoService = {
getTodoItems: () => gql`
query todoQuery {
todo(where: { userId: { _eq: "some_hard_coded_id" } }, order_by: { createdAt: desc }) {
id
todo {
title,
body,
status
}
userId
}
}
}
`
It seems you missed a $ sign
try:
import { gql } from '#apollo/client';
export const todoService = {
getTodoItems: () => gql`
query todoQuery($loggedUserId: String!) {
todo(where: { userId: { _eq: $loggedUserId } }, order_by: { createdAt: desc }) {
id
todo {
title,
body,
status
}
userId
}
}
}
`
this worked for me.
import { gql } from '#apollo/client';
export const todoService = {
getTodoItems: () => gql`
query todoQuery($loggedUserId: uuid! = loggedUserId) {
todo(where: { userId: { _eq: $loggedUserId } }, order_by: { createdAt: desc }) {
id
todo {
title,
body,
status
}
userId
}
}
}
`

Get data from a query in ReactJs using GraphQl

I use Apollo Client and have the next code:
import {InMemoryCache, makeVar} from "#apollo/client";
export const cartItemsVar = makeVar(['test']);
export const demo3 = makeVar({name: 123});
export const cache = new InMemoryCache({
addTypename: true,
typePolicies: {
Person: {
fields: {
nameCar: {
read(name) {
return demo3();
}
}
}},
Query: {
fields: {
cartItems: {
read() {
return cartItemsVar();
}
},
demo: {
merge(name) {
return demo3()
}
}
}
}
}
});
How you can notice, this is my store in my react application. I want to get data from
Person: {
fields: {
nameCar: {
read(name) {
return demo3();
}
}
}
},
But doing something like this
const GET_CART_ITEMS = gql `
query GetCartItems {
nameCar #client
}
`;
//
const {
data,
loading,
error
} = useQuery(GET_CART_ITEMS);
... i get undefined in data., but if i do
export const GET_CART_ITEMS = gql `
query GetCartItems {
cartItems #client
}
`;
...i get the wanted result cartItems: ["test"].
Question: How to get data {name: 123} in the first situation and why the code does not work? What is the difference inside the typePolicies between Person and Query?

What is the right way to read cache after writing to cache from react apollo-client?

I have created a basic example that reads data from a graphql server.
I have noticed my network calls make a request after I have run my mutation.
I am trying to update the cache to prevent that network call but when I then try to read the cache.
This method works to retrieve data, but it does not automatically read from the cache:
useQuery
const { data } = useQuery(GET_PLAYERS);
useQuery with #client directive to indicate get cache - fails
const GET_PLAYERS = gql`
query getPlayers {
players #client {
__typename
_id
name
score
}
}
`;
const { data } = useQuery(GET_PLAYERS);
err =
MissingFieldError {message: "Can't find field 'players' on ROOT_QUERY object", path: Array(1), query: {…}, variables: {…}}
message: "Can't find field 'players' on ROOT_QUERY object"
using client.readQuery() returns nothing on both load and after mutation is still no results:
const obj = client.readQuery({ query: GET_PLAYERS });
Here is my full code.
import React, { FC } from 'react';
import 'cross-fetch/polyfill'; // patch for tests: Error: fetch is not found globally and no fetcher passed, to fix pass a fetch for your environment
import {
gql,
useQuery,
useMutation,
ApolloClient,
InMemoryCache,
ApolloProvider
} from '#apollo/client';
const uri = 'http://localhost:4000/graphql';
const client = new ApolloClient({
uri,
cache: new InMemoryCache()
});
const ADD_PLAYER = gql`
mutation AddPlayer($name: String!) {
addPlayer(player: { name: $name }) {
_id
name
score
}
}
`;
// example with directive = #client to try to get cached version
// const GET_PLAYERS = gql`
// query getPlayers {
// players #client {
// __typename
// _id
// name
// score
// }
// }
// `;
const GET_PLAYERS = gql`
query getPlayers {
players {
_id
name
score
}
}
`;
interface IData {
data: {
players: Array<{ name: string }>;
};
loading: boolean;
}
interface IPlayer {
name: string | null;
}
const App: FC = () => {
const { data } = useQuery(GET_PLAYERS);
// const { data } = client.readQuery({ query: GET_PLAYERS });
// console.log('obj = ', obj);
// const data = obj && obj.data;
const [addPlayer] = useMutation(ADD_PLAYER, {
update: (cache, { data: mutationData }) => {
try {
const cacheGetPlayers: { players: Array<IPlayer> } | null = cache.readQuery({
query: GET_PLAYERS
});
const cachePlayers: Array<IPlayer> = (cacheGetPlayers && cacheGetPlayers.players) || [
{ name: null }
];
let playersUpdated: Array<IPlayer> = { ...cachePlayers };
const player = mutationData.addPlayer;
if (player) {
// ie not already existing !!!
if (playersUpdated) {
if (!cachePlayers.find((item: IPlayer) => player.name !== item.name)) {
playersUpdated.push(player);
}
} else {
playersUpdated = [player];
}
cache.writeQuery({
query: GET_PLAYERS,
data: {
getPlayers: {
players: playersUpdated
}
}
});
}
} catch (err) {
console.log('err = ', err);
}
}
});
const handleClick = () => {
const name = 'mary';
addPlayer({
variables: { name },
// example data:
// optimisticResponse: {
// __typename: 'Mutation',
// addPlayer: {
// _id: ObjectId('4edd40c86762e0fb12000003'), // 4edd40c86762e0fb12000003
// score: 0,
// name
// }
// },
refetchQueries: [{ query: GET_PLAYERS }]
});
};
return (
<div>
list of existing names = {data &&
data.players instanceof Array &&
data.players.map((item: { name: string }) => `,${item.name}`)}
<button type="button" onClick={handleClick}>
Click to add player
</button>
</div>
);
};
const Prov = () => (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
export default Prov;
Any advice, thanks
Firstly, #client directive just uses for querying data that you should initiate a players field in the cache with the __typename. Like this:
const cache = new InMemoryCache()
const client = new ApolloClient({
cache,
link,
})
cache.writeData({
data: {
players: {
items: [],
__typename: Player
}
},
})
...
const GET_PLAYERS = gql`
query {
players #client {
__typename
items {
_id
name
score
}
}
}
`;
const { data } = useQuery(GET_PLAYERS);
Secondly, if you want to update the cache after a mutation you should use update option. Like this:
useMutation(UPDATE_PLAYER, {
update(cache, { data: { player } }) {
const { players } = cache.readQuery(options)
cache.writeQuery({
...options,
data: {
players: { ...players, items: [player, ...players.items] },
},
})
},
}
}
)

Resources