{ apollo-client, react, grapgql } what is good way handle multiple loadings? - reactjs

i am using react and apollo-client with graphql on Node.js
i have component which call three Query
so i have three loading like this
function MyComponent({
IsLoggedIn: { auth: { isLoggedIn } = {}, loading } = {},
GetUserInfo: { GetMyProfile: { user } = {}, loading: loading2 } = {},
lastSelectedHouse: { auth: { lastSelectedHouse } = {}, loading: loading3 } = {},
}) (
{ (!loading && !loading2 && !loading3) ? <MyComponent {...props} > : "loading" }
)
export default compose(
graphql(SELECTED_HOUSE, { name: 'lastSelectedHouse' }),
graphql(IS_LOGGED_IN, { name: 'IsLoggedIn' }),
graphql(GET_USER_INFO, {name: 'GetUserInfo'})
)(JDmiddleServer);
i don't wan't handle three loading
how can i combine theme?
here is my Queries
export const IS_LOGGED_IN = gql`
{
auth {
isLoggedIn #client
}
}
`;
export const SELECTED_HOUSE = gql`
{
auth {
lastSelectedHouse #client {
label
value
}
}
}
`;
export const GET_USER_INFO = gql`
query {
GetMyProfile {
user {
_id
name
phoneNumber
password
email
isPhoneVerified
checkPrivacyPolicy
}
}
`;
Yes. two Query is for #client and one is for #server
is it possible merge these Queries?

Yes, it's possible to combine the queries even though one is for the server and two for the client.
Something like this should work:
export const GET_USER_INFO = gql`
{
auth {
isLoggedIn #client
lastSelectedHouse #client {
label
value
}
}
}
query {
GetMyProfile {
user {
_id
name
phoneNumber
password
email
isPhoneVerified
checkPrivacyPolicy
}
}
`;
Depending on your schema and resolvers you should be able to design something like below which would be better.
Check the docs for combining queries: https://www.apollographql.com/docs/link/links/state.html#combine
export const GET_USER_INFO = gql`
query {
GetMyProfile {
user {
isLoggedIn #client
lastSelectedHouse #client {
label
value
}
_id
name
phoneNumber
password
email
isPhoneVerified
checkPrivacyPolicy
}
}
`;

Related

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] },
},
})
},
}
}
)

Reuse mutations without duplicating code in Apollo + React?

I have the mutation below in a React Component. Im going to need the same mutation in multiple components and on different pages.
How can I reuse my mutation code without repeating it?
This example isn't that complex but some queries use optimistic UI and write to the store.
import React from 'react';
import { graphql, compose } from 'react-apollo';
import { gql } from 'apollo-boost';
const JoinLocation = props => {
if (props.ME.loading) return null;
const { locationMachineName } = props;
const me = props.ME.me;
const submit = () => {
props
.JOIN_LOCATION({
variables: {
userId: me.id,
locationMachine: locationMachineName,
},
})
.catch(err => {
console.error(err);
});
};
return <button onClick={() => submit()}>Join location</button>;
};
const ME = gql`
query me {
me {
id
}
}
`;
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export default compose(
graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' }),
graphql(ME, { name: 'ME' }),
)(JoinLocation);
Create a higher-order component (HOC) for the mutation/query that contains the gql options and optimistic UI logic:
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export const withJoinLocation = component => graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' })(component);
Then wrap your different components with it.
export default withJoinLocation(JoinLocation);
UPDATE: Based on your below comment, if you want to encapsulate the whole submit logic and not just the mutation as stated in your question, you can use a render prop like so:
import React from 'react';
import { graphql, compose } from 'react-apollo';
import { gql } from 'apollo-boost';
const JoinLocation = props => {
if (props.ME.loading) return null;
const { locationMachineName } = props;
const me = props.ME.me;
const submit = () => {
props
.JOIN_LOCATION({
variables: {
userId: me.id,
locationMachine: locationMachineName,
},
})
.catch(err => {
console.error(err);
});
};
return props.render(submit);
};
const ME = gql`
query me {
me {
id
}
}
`;
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export default compose(
graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' }),
graphql(ME, { name: 'ME' }),
)(JoinLocation);
Now any component can consume the reusable submit logic. Assume you name the above component JoinLocation.js:
import JoinLocation from './JoinLocation';
const Comp = () => {
return <JoinLocation render={submit => <button onClick={() => submit()}>Join location</button>}/>
}

How return data from a GraphQL mutation?

I have the following redux-form below... How can I get the console.log to obtain the user.id from the graphQL mutation result data?
const withForm = compose(
withMySignupMutation,
reduxForm({
...
onSubmit: async (values, dispatch, {
mySignup,
}) => {
try {
const result = await mySignup(values);
console.log('I Need user.id here');
console.log(result.user.id);
...
withMySignupMutation
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
export const SIGNUP_MUTATION = gql`
mutation (
$email: String!
$name: String!
) {
signup(authProvider: {
mySignup: {
email: $email
name: $name
}
}) {
token
}
}
`;
export default graphql(
SIGNUP_MUTATION,
{
props: ({ mutate }) => ({
mySignup: async (variables) => {
const { data } = await mutate({ variables });
},
}),
}
);
Thank you! happy holidays
In my opinion, it should be result.data.signup.id but we need to see the resolvers and your schema definition. It depends on what the resolvers are sends back. It should be a User type.
Anyway, you must ask for the id in your mutation otherwise it won't get resolved:
export const SIGNUP_MUTATION = gql`
mutation (
$email: String!
$name: String!
) {
signup(authProvider: {
mySignup: {
email: $email
name: $name
}
}) {
token
id ### <===HERE===
}
}
`
And if you have a graphiql access to the server:
click on the top-right corner the DOCS button, then
click mutation: Mutation
then you should see something like:
signup(authProvider: AuthProviderNewcomerData!): User
Then you know you will get a User back.

Resources