update function in the useMutation hook displays the mutation result as undefined - reactjs

I'm new to GraphQL and had a question about useMutation hook. I want to be able to update the UI by updating the object that has been modified instead of refreshing the UI. The problem is that when i pass the result of useMutation hook to the update function, it displays the result as undefined, but the query goes through and returns the response
I'm executing the useMutation hook on click of a button
<button
onClick={() => {
addTodo('Placeholder') : undefined;
}}
></button>
Button click calls a function called addTodo which executes useMutation hook
const ADD_TODO = gql`
mutation AddTodo($text: String!) {
addTodo(text: $text) {
id
text
}
}
`;
const addTodo = (text: string) => {
addTodo({ variables: { type: text } });
}
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
console.log('MUTATION DATA IS ', addTodo);
}
});
The above console.log displays undefined always whereas in the Network tab I use the query being executed successfully. The documentation says that update function is passed a cache object and the result of the mutation addTodo but for me the result of the mutation is undefined. What am I doing wrong?

Couple of things to pay attention to:
addTodo('Placeholder') : undefined; this syntax looks weird. Considering it is a typo for the purpose of showing the example here on stackoverflow.
your mutation expects text as a variable:
const ADD_TODO = gql`
mutation AddTodo($text: String!) {
addTodo(text: $text) {
id
text
}
}
`;
while you pass variable type when calling mutation:
addTodo({ variables: { type: text } });
try passing text:
addTodo({ variables: { text } });

Related

How to execute React custom hook only when data is available or Apollo client loading is false

I have custom React hook which adding some scripts and add a variable to window object:
const useMyHook = ({ id, type }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
window.mydata = { id: `${id}`, type: `${type}` };
}, [id]);
};
I am using Apollo client and GraphQl for fetching data.
In my Page component, when I console.log(myData) the data returns undefined and then right after it returns the data (without refreshing). I am using Fragments.
From useQuery hook I can get the loading variable. How do I have to use loading and my custom hook so when loading === false -> use my custom hook.
I tried something like this:
const foo = useMyHook({ id: myData.id, type: myData.type });
Then below in the component in the return:
return (
{!loading && foo}
// Rest of the component jsx code
)
But still sometimes it returns first undefined?
How can I fix this issue?
# Update:
For now I added another prop loading: boolean and added this to the custom hook:
useEffect(() => {
if (!loading) {
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [id]);
Is this correct approach. And why does my fragment query first returns undefined?
You can add another prop like enabled for handling this issue
sth like this:
useMyHook.tsx :
const useMyHook = ({ id, type,enabled }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
if(enabled)
{
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [enabled]);
};
other component:
const foo = useMyHook({ id: myData.id, type: myData.type,enabled:!loading && (data || error) });
return foo
and you need to use data and error that can be deconstructed from useQuery (like loading ) to just make sure if loading is false and data or error exists (that's because of first time that the component renders, loading is false but and data and error doesn't exists because request is not submitted yet, that's why you see undefined first )

Stop `useMutation` from re-rendering component and ignore result

I have a mutation called like this (this isn't the actual mutation call, but a minimal example):
const App = () => {
const [myMutation] = useMutation(gql`
mutation Test($updateUserId: ID!, $updateUserInput: UpdateUserInput!) {
updateUser(id: $updateUserId, input: $updateUserInput) {
id
firstname
age
}
}
`);
// Runs every time this component is rendered
console.log("rendered");
// Called when the button is clicked
const update = () => {
myMutation({
variables: {
updateUserId: 1,
updateUserInput: {
age: Math.round(Math.random() * 10 + 5) // Set a random age from 5 - 15
}
}
});
};
return (
<>
<h1>Update Test</h1>
<button onClick={update}>Update!</button>
</>
);
};
Whenever onClick is called, the whole component is re-rendered. This is not what I want, as I don't care about the result of the mutation. Is there any way to stop myMutation from causing a re-render, and ignore the result completely?
useMutation is a hook and it has state in it which cause a re-render
See the useMultation type bellow:
export interface MutationResult<TData = any> {
data?: TData | null;
error?: ApolloError;
loading: boolean;
called: boolean;
client: ApolloClient<object>;
}
The loading state is the one causing re-render, so in which component you delcare useMutation it will re-render due to that state
One possible fix is to separate the component with useMutation to another one
Something like this:
function App() {
return (
<>
<Header /> // <-- this coponent won't re-render
<UpdateUserButton /> // <-- useMutation is in here!
</>
);
}
Checkout the live example:
Well, if you don't need the data returned by the mutation, you can also pass the ignoreResults option. Data will not be returned from the mutation hook but loading would called neither which prevents a re-render.
https://www.apollographql.com/docs/react/data/mutations/#ignoreresults
const [someMutation] = useSomeMutation({
ignoreResults: true,
});
This is likely because the mutation is returning a normalised object which this component or a higher component depends on via a hook like useQuery.
If you don't care about the result of the mutation make sure it doesn't return the normalised object.
Something like
mutation SomeMutation ($input: SomeMutationInput!){
someMutation (input: $input){
ok
}
}
Following should work (not tested)
Since you don't want re-render, you don't need to use useMutation hook which has state.
Instead you can directly import you apollo-client and use mutate on it without using useMutation hook.
You can choose to use the result in .then function or get rid of it.
import { apolloClient } from '../yourApolloClientFile';
const App = () => {
const myMutation = (vars) => apolloClient.mutate({
mutation: gql`
mutation Test($updateUserId: ID!, $updateUserInput: UpdateUserInput!) {
updateUser(id: $updateUserId, input: $updateUserInput) {
id
firstname
age
}
}
`,
variables: vars
})
.then(result => console.log(result))
.catch(error => console.log(error))
// Runs every time this component is rendered
console.log("rendered");
// Called when the button is clicked
const update = () => {
myMutation({
updateUserId: 1,
updateUserInput: {
age: Math.round(Math.random() * 10 + 5) // Set a random age from 5 - 15
}
});
};
return (
<>
<h1>Update Test</h1>
<button onClick={update}>Update!</button>
</>
);
};

Apollo Client: Can't put ReactHook after Conditional, but React Hook needs data available only after conditional?

Order of Operations issues it seems.
Can't use a React Hook after a Conditional as I get this error:
Error: Rendered more hooks than during previous render
This React Hook is 3rd party and can't change it. This React Hook needs data that is only available after the conditional...so I get another error of data not defined
Ok, how about declare a new variable (empty array) to put in 3rd party React hook before conditional? Then after conditional reassign with data that shows up. Doesn't work either... as I get error that it is using the same key...well because the 3rd party hook is getting an empty array variable first. What do I do?
import { libraryHook } from '3rd party'
import { useQuery } from '#apollo/client'
const myComponent = () => {
const {loading, error, data } = useQuery(MY_QUERY, { variables: {someVariable: fromDatabase}
const { 3rdpartyVariable } = libraryHook(data);
// Hook needs to be above conditional so I don't get error,
//but data only available after conditional.
if (loading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
console.log(data);
return(
<div>
{data}
</div>
)
}
export default myComponent;```
There is another hook called useLazyQuery that can be used in this case, it provides you a function that can be called to execute your query. This is how you can do it
import { gql, useLazyQuery } from "#apollo/client";
const GET_GREETING = gql`
query GetGreeting($language: String!) {
greeting(language: $language) {
message
}
}
`;
function Hello() {
const [loadGreeting, { called, loading, data }] = useLazyQuery(
GET_GREETING,
{ variables: { language: "english" } }
);
if (called && loading) return <p>Loading ...</p>
if (!called) {
return <button onClick={() => loadGreeting()}>Load greeting</button>
}
return <h1>Hello {data.greeting.message}!</h1>;
}
Refer Docs

Refetch queries after Mutation doesn't work

I have mutation as follows:
<Mutation
mutation={ADD_NEW_SLOT}
refetchQueries={() => [{ query: GET_COMPANY_ADDRESSES, variables: { companyId: this.props.session.company.id } }]}
awaitRefetchQueries={true}
>
.......
</Mutation>
Where GET_COMPANY_ADDRESSES is exported from a parent component.
But it doesn't refresh after the mutation is done.
What am I doing wrong?
UPDATE
The return of the render function in the parent component is as follows:
<Query query={GET_COMPANY_ADDRESSES} variables={{companyId: session.company.id}} notifyOnNetworkStatusChange={true} fetchPolicy={'cache-and-network'}>
{({loading, error, refetch, data}) => {
if (loading) return <LoadingIndicator/>;
if (error) return <ErrorIndicator description={error.message}/>;
const treeNodes = convertSlotsToTree(data);
const address = data.companyAddresses[1];
return (
<AddSlot address={address}
toggleSlotForm={this.props.togglePanel}
session={this.props.session}/>
)
}}
</Query>
The graphql query is in the same file and it is as follows:
export const GET_COMPANY_ADDRESSES = gql`
query CompanyAddresses($companyId: Int!) {
companyAddresses(companyId: $companyId) {
id
name
default
compound
address {
id
addressFull
countryCode
city
postCode
slotSet{
id
area
zone
aisle
side
level
position
disabled
col
printEntry
fullName
}
}
}
}
`;
It still does not work with react-apollo 3.1.x w/o workaround.
It seems that if you use these two things, updates will be sent to Query:
Set option for Query: fetchPolicy="cache-and-network" (you use this already).
Comment refetchQueries and use Query's refetch instead. Attach function to Mutation's onCompleted, and call refetch from the Query directly. You should see graphql query in HTTP requests, and UI should updates itself as well.
Note. "cache-first" as fetchPolicy did not work, although I guess cache is updated, but UI does not.
With Angular I found this working, as expected (note the "no-cache" flag):
this.postsQuery = this.apollo.watchQuery<any>({
query: GET_USER_ON_ROLE,
fetchPolicy: "no-cache",
variables: {
roleId : this.roleId
}
});
this.querySubscription = this.postsQuery
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.allUsers = data.getAllUsersPerRoleUI;
});
An you need to reload once the mutation is over using refetch():
public reloadFunction()
{
this.postsQuery.refetch()
}

Passing ID as correct variable type in GraphQL Mutate function

I'm trying to edit an item using GraphQL by passing in arguments using this.props.mutate. I'm getting following error... Error: GraphQL error: Variable $id of required type ID! was not provided. Hence, the problem lies in me passing the wrong ID type to the mutate function. Anyone know how to pass the ID type as the correct type to the mutate function? or can I cast the ID type from string to the correct type to pass as variable to the mutate function? Thank you
i'm using local component state to hold the values to prefill a form in case you are wondering why I am using local state
import UPDATE_CHAT_MUTATIONS from '../graphql/mutations/updateChat';
class EditChat extends React.Component {
state = {
text: '',
id: ''
}
componentWillMount() {
this._onEditLoad()
}
_onEditLoad = () => {
const chat = this.props.navigation.state.params;
this.setState({ text: chat.text, id: chat._id })
}
_onChangeText = text => this.setState({ text });
_onEditPress = async () => {
const { id, text } = this.state;
await this.props.mutate({
variables: {
_id: id,
text
}
});
Keyboard.dismiss();
this.props.navigation.goBack(null);
}
i managed to get it to work! I made an error on the graphql mutations on the client side. Below is the code that works!! Hope this will help those who face the same issue. Cheers
import { gql } from 'react-apollo';
export default gql`
mutation updateChat($_id: ID!, $text: String!) {
updateChat(_id: $_id, text: $text) {
text
_id
updatedAt
}
}
`;

Resources