Reputation: 759
Seen similar issues here, but couldn't wrap my mind around those. I'm new to functional components and Apollo Client, and, after reading the documentation, created a component that should render a list of items, then (upon adding), update the cache and read from it, rendering the component again. The result is different, though, and I can't understand why the component is not rendering. Apollo Client and reactive variables are used.
Here's my cache.js
:
import { InMemoryCache, makeVar } from "@apollo/client";
export const cache = new InMemoryCache({
dataIdFromObject: (object) => object.key,
typePolicies: {
Query: {
fields: {
items: {
read() {
return itemsVar();
},
},
},
},
},
});
export const itemsVar = makeVar([]);
export default cache;
Then, I have the following component:
import React, { useState, Fragment } from "react";
import { useQuery, useReactiveVar, useMutation } from "@apollo/client";
import GET_ALL_ACTIVE_ITEMS from "../../queries/Item";
import CREATE_ITEM from "../../mutations/Item";
import { itemsVar } from "../../cache";
//Components
import Item from "./Item";
const ItemContainer = () => {
// this gets the list of items upon component loading
const { data } = useQuery(GET_ALL_ACTIVE_ITEMS);
// Input to create the new Item
const [itemInput, setItemInput] = useState({
title: "",
});
const [createItem] = useMutation(CREATE_ITEM);
const onAddItemClick = (e) => {
e.preventDefault();
createItem({
variables: {
itemInput: itemInput,
},
}).then((res) => {
// Gets the result of the created Item and adds to cached
let resArray = [...res.data.itemInput];
itemsVar([...itemsVar().getAllActiveItems, resArray]);
});
};
const onChange = (e) => {
setItemInput({ ...itemInput, [e.target.name]: e.target.value });
};
// Sets the cache reactive variable to the data from the backend
itemsVar(data);
const items = useReactiveVar(itemsVar);
return (
<div>
<div>
<div onClick={onAddItemClick} className="primary-button">
Add item
</div>
<input
type="text"
name="title"
value={itemInput.title}
onChange={onChange}
/>
</div>
{items && items.getAllActiveItems.length === 0 ? (
<p>No items</p>
) : (
<Fragment>
{items &&
items.getAllActiveItems.map(({ _id, title }) => (
<Item key={_id} title={title} />
))}
</Fragment>
)}
</div>
);
};
export default ItemContainer;
This components properly renders the items on loading. Now, even though it creates a new item, it doesn't refresh the component (I assume the cache is not updated too, though not sure).
Get all items query:
import gql from "graphql-tag";
const GET_ALL_ACTIVE_ITEMS = gql`
query getAllActiveItems() {
getAllActiveItems() {
_id
title
}
}
`;
export default GET_ALL_ACTIVE_ITEMS;
Create item query, it creates an item and returns it as an array:
import gql from "graphql-tag";
const CREATE_ITEM = gql`
mutation createItem($itemInput: CreateItemInput!) {
createItem(itemInput: $itemInput) {
_id
title
}
}
`;
export default CREATE_ITEM;
Can you help me understand what am I missing here?
Upvotes: 1
Views: 1984
Reputation: 2771
You are NOT specifying if you want to update the cache or not in useMutation hook.
The useMutation hook accepts options as well, like this:-
function useMutation<TData = any, TVariables = OperationVariables>(
mutation: DocumentNode,
options?: MutationHookOptions<TData, TVariables>,
): MutationTuple<TData, TVariables> {}
So, if you want to update the cache after a mutation occurs, use
const [createItem] = useMutation(CREATE_ITEM,{update: updateCache});
const updateCache = (cache, {data}) => {
// Fetch from the cache
const existingItems = cache.readQuery({
query: GET_ALL_ACTIVE_ITEMS
});
// Add to the cache
const newItem = ....
cache.writeQuery({
query: GET_ALL_ACTIVE_ITEMS,
data: {items: [newItem, ...existingItems]}
});
};
Upvotes: 2