Query data doesn't update after successful apollo cache write - reactjs

I have a query on my App.js:
import { gql } from 'apollo-boost';
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
challenges {
id
title
}
goals {
id
title
completed
createdAt
updatedAt
steps {
id
completed
title
}
}
}
`;
And i am looking to write a simple deleteGoal mutation:
const DeleteWrapper = (props) => {
const [deleteGoal, { data }] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, payload) {
const data = cache.readQuery({ query: ALL_ITEMS_QUERY });
data.goals = data.goals.filter(
(goal) => goal.id !== payload.data.deleteGoal.id
);
cache.writeQuery({ query: ALL_ITEMS_QUERY, data });
},
});
}
The function returns the modified array correctly, but the item never disappears from the frontend list. I have a hunch that this is related to querying multiple categories at once (goals and challenges, rather than goals only).
Even though the cache seems to be modified correclty, why does the item never disappear, why does the re-render never happen?

After some trial and error I found out that I have to lay out the exact data object to the writeQuery function. I don't really understand why, since the challenges object was left untouched after the query. I have not been able to make this work otherwise.
const DeleteWrapper = (props) => {
const [deleteGoal] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, { data: { deleteGoal} }) {
const { goals, challenges } = cache.readQuery({ query: ALL_ITEMS_QUERY });
const newArr = goals.filter((goal) => goal.id !== deleteGoal.id);
cache.writeQuery({
query: ALL_ITEMS_QUERY,
data: { challenges, goals: newArr },
});
},
});
}

Related

How to pass Unique Id in Graphql query

I'm trying to pass a unique id to a GraphQL query to get all data against the id. But when I print the console it shows undefined. Note that if I want to see all posts the then it becomes successful by another query.
in my query.js file I have written the below query:
export const Unique_Post_Query= gql`
query SampleQueries($id: ID!) {
post(_id: $id) {
id
data {
title
body {
text
}
}
}
}`;
and the below code from another file where I'm trying to see the result of the query:
const id='e42fd2b5-b84a-4417-afd2-36cdbaa204dd';
const { data , error,loading} = useQuery(Unique_Post_Query, { variables: {id ,}, });
//const { error, loading, data } = useQuery(PROFILE_QUERY);
console.log('yourrrrr f data',data);
Please check below reference
const id='e42fd2b5-b84a-4417-afd2-36cdbaa204dd';
const { loading, data } = useQuery(Unique_Post_Query, {
variables: { id: id},
});
useEffect(() => {
if (data && data?.post) {
console.log("data: ", data?.post);
}
}, [data]);

Using fetchMore to fetch ALL data on component mount

I have a situation where I need to fetch e.g. all articles posted by a user when a component is mounted. To get a user's articles I am using the following query:
const GET_USER_ARTICLES = gql`
query getUserArticles($id: ID, $numArticles: Int!, $cursor: String) {
user(id: $id) {
id
articles(first: $numArticles, after: $cursor, orderBy: "-created", state: "enabled") #connection(key: "userArticles") {
edges {
node {
name
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`;
If there is a next page I want to keep fetching more articles until I have ALL of them. Up until now I haven't had the need to do anything like this (normally I have a button the user can click "Load more" to fetch more articles for example, but now need to fetch everything without a user interacting with anything), so I'm not sure what the best way to go about this is.
An example of the query in React:
const PAGE_SIZE = 10;
const { data, loading, fetchMore } = useQuery<UserArticlesData, UserArticlesVariables>(
GET_USER_ARTICLES,
{ variables: { id: userId, numArticles: PAGE_SIZE, cursor: null } },
);
I am a little lost how I can use the fetchMore to keep fetching until there aren't any more pages left, while also showing a loading state to the user. I'm also not sure this is the best way to go about this in the first place, so any suggestions are more than welcome!
If the API does not limit the page size, you could just provide an arbitrarily large number as the page size to get the remaining results. Assuming the page size can only be so big, though, you can do something like this:
const { data, loading, fetchMore } = useQuery(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor: null },
notifyOnNetworkStatusChange: true,
})
const fetchRest = async () => {
const { user: { articles: { pageInfo } } } = data
const updateQuery = (prev, { fetchMoreResult }) => {
// Merge the fetchMoreResult and return the combined result
}
let hasNextPage = pageInfo.hasNextPage
let cursor = pageInfo. endCursor
while (hasNextPage) {
const { data } = await fetchMore({
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
updateQuery,
})
const { user: { articles: { pageInfo } } } = data
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
}
By setting notifyOnNetworkStatusChange to true, loading will be updated whenever fetchMore is doing any fetching. Then we just loop until hasNextPage is called. fetchMore returns a Promise that resolves to the query result, so we can use the query response outside the updateQuery function.
Note that this is a rough example -- you might actually want to keep track of loading state yourself, for example. If your API has rate limiting, your logic should account for that as well. However hopefully this gives you a good starting point.
Edit:
If you need to get all the articles initially, I wouldn't use useQuery and fetchMore at all. The easiest workaround would be to manage the data and loading state yourself and utilize client.query instead.
const client = useApolloClient()
const [data, setData] = useState()
const [loading, setLoading] = useState(true)
const fetchAll = async () => {
let hasNextPage = true
let cursor = null
let allResults = null
while (hasNextPage) {
const { data } = await client.query(GET_USER_ARTICLES, {
variables: { id: userId, numArticles: PAGE_SIZE, cursor },
})
// merge data with allResults
hasNextPage = pageInfo.hasNextPage
cursor = pageInfo. endCursor
}
setLoading(false)
setData(allResults)
}
useEffect(() => {
fetchAll()
}, [])

Apollo Client updateQuery Missing field [fieldName] in {}

On a specific Query subscribeToMore function I can't seem to get the Query to take the new data.
updateQuery: async (prev, { subscriptionData } ) => {
const posts = [
...prev.getPosts,
subscriptionData.data.postCreated
];
const result = {
...prev,
count: posts.length,
getPosts: posts
}
return result;
}
This results in the following error:
Missing field getPosts in {}
If I log the result I get:
Object { getPosts: (3) […], count: 3 }
I'm not sure what is causing this as it seems fine on several other updateQuery calls. This one in particular seems to break.
The queries in question:
export const POST_CREATED = gql`
subscription postCreated($authorId: ID!){
postCreated(authorId: $authorId){
id, title, createdAt, published
}
} `;
export const GET_POSTS = gql`
query getPosts($authorId: ID!){
getPosts(authorId: $authorId){
id, title, createdAt, published
}
} `;
Got the same kind of Problem.
I fixed it because the return Object was wrong.
Since you´re only adding the post to the end this should work:
updateQuery: async (prev, { subscriptionData } ) => {
prev.getPosts.push(subscriptionData.data.postCreated)
return prev;
}

Testing with React Apollo ignores variables

I have fairly simple test setup very similar to what's coming with tutorial repo from Apollo GraphQL and described in their documentation.
Here's the query:
const GET_USER_WITH_SPEND_CONTROL = gql`
query GetUserWithSpendControl($userToken: ID!) {
me(token: $userToken) {
firstName
lastName
spendControl {
amountAvailable
currencyCode
}
}
}
`;
Here's the test:
const mockUserSpendControl = {
me: {
firstName: "Andrii",
lastName: "Gorishnii",
spendControl: {
amountAvailable: 1000,
currencyCode: "USD",
}
}
};
describe('Account Summary', () => {
afterEach(cleanup);
it('renders account summary', async () => {
const mocks = [
{
request: { query: GET_USER_WITH_SPEND_CONTROL },
result: {
data: mockUserSpendControl
},
},
];
const { getByText } = await renderApollo(<AccountSummary/>, {
mocks,
cache,
addTypename: false
});
await waitForElement(() => getByText(/Gorishnii/i));
});
});
The wrong thing about this test is that it works while it shouldn't: GQL schema declares required variable userToken however if I add it to the test like this:
...
request: { query: GET_USER_WITH_SPEND_CONTROL, variables: {userToken: "56aed"} },
...
It actually fails the test.
Does anyone have similar problems with testing React Apollo components?
P.S. I'm having bigger problem where my original query goes like this:
const GET_USER_WITH_SPEND_CONTROL = gql`
query GetUserWithSpendControl($userToken: ID!) {
currentUserToken #client #export(as: "userToken")
me(token: $userToken) {
firstName
lastName
spendControl {
amountAvailable
currencyCode
}
}
}
`;
And I can never match the mocked result with the query which is using variables exported from client's cache. But this will probably be next question after I solve "simpler" problem.

React / Graphql / Apollo, Not refetch data after update states and change routes

I have a graphql query that shows me a list of users, when I update the list with a mutation, the state change well, but when I change my route and I come back to the old one, it return the old state.I have to do a hard refresh on my browser to have the new list.
This is my query :
export const group = (id) => {
const data = Client.query({
query: gql`
query group($id: Int) {
group(_id: $id) {
_id
name
lat
lng
is_private
creation_date
}
}
})
This is my component :
async componentWillMount() {
try {
var data = await group(632);
var result = data.data.group[0];
this.setState({
group: result
});
} catch (e) {
console.log(e);
}
}
updateGroup = async() => {
try {
var data = await groupUpdate(501, 632, {
name: this.state.group.name,
is_private: this.state.group.is_private,
address: this.state.group.address,
creation_date: this.state.group.creation_date,
nbrEmployees: this.state.group.nbrEmployees
});
notifyUser(NOTIFY.success, "Done");
this.toggleInfo();
} catch (e) {
notifyUser(NOTIFY.error, "Error Serveur");
}
}
Any help please ?

Resources