First of all, i'd like to apologize if this is a duplicate but none of the existing answers in similar questions helped me out.
I am using nextjs 9.5.3 and apollo client 3.2.2 . I have a form where a user fills it out to update their profile details. On submission, the data saved is saved in the database and a response returned back to the client. The issue is that the response is unable to update the cache but it can be found inside ROOT_MUTATION according to apollo devtools.
I use a query to initially load the user's data then update the cache with the result during the mutation. Below are the local query and mutation.
// fragments
export const editUserProfileFragment= gql`
fragment EditUserProfileFields on ProfileInterface {
id
type
slug
name
location {
name
address
country
latitude
longitude
}
settings
createdAt
isActive
}
`;
// query
export const editUserProfileQuery = gql`
query EditUserProfile($slug: String) {
Profile(slug: $slug) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
// mutation
export const editUserProfileMutation = gql`
mutation EditUserProfile($id: ObjectID!, $profile: ProfileInput!) {
editProfile(id: $id, profile: $profile) {
...EditUserProfileFields
}
}
${editUserProfileFragment}
`;
Here's how i use the query and mutation:
// the query
const { error, data, loading } = useQuery(editUserProfileQuery, {
variables: { slug },
fetchPolicy: "network-only",
})
// data returns a `Profile` object
// the mutation
const [editUserProfileMutate] = useMutation(editUserProfileMutation)
...
// save data
try {
const response = await editUserProfileMutate({
variables: { id, profile: inputdata },
// update: (cache, { data }) => {
// const cachedData: any = cache.readQuery({
// query: editUserProfileQuery,
// variables: { slug: newProfile.slug }
// });
// const cacheId = cache.identify(data.editProfile) // to see the id, i wanted to try cache.modify() but didn't how to proceed.
// console.log('update.cachedData', cachedData);
// console.log('update.cachedData.Profile', cachedData.Profile);
// console.log('update.data', data);
// const newData = { ...cachedData.Profile, ...data.editProfile }; // i first used [] but
// console.log('newData', newData);
// // cache.writeQuery({
// // query: editUserProfileQuery,
// // variables: { slug: newProfile.slug },
// // data: { editProfile: newData }
// // })
// // cache.modify({
// // id: cacheId,
// // })
// },
// tried the below but didn't work
// refetchQueries: [{
// query: editProfilePageQuery,
// variables: { slug: newProfile.slug },
// }],
// awaitRefetchQueries: true
});
const updatedProfile = response.data.editProfile;
console.log('updatedProfile', updatedProfile);
....
} catch (error) {
....
} // trycatch
Also the below apollo client is mainly based on nextjs with-apollo example:
...
let apolloClient;
...
const cache = new InMemoryCache({
// https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces
dataIdFromObject: result => `${result.__typename}:${result._id || result.id || result.name || result.slug || Math.floor(Math.random() * 1000000)}`,
possibleTypes: {
ProfileInterface: ["Star", "User"],
},
// #see https://www.apollographql.com/docs/react/caching/cache-field-behavior/#merging-non-normalized-objects from console warnings
typePolicies:
User: {
fields: {
location: {
merge(_existing, incoming) {
return incoming;
},
},
},
},
},
});
function createClient() {
const link = makeLink();
return new ApolloClient({
cache,
link,
connectToDevTools:typeof window !== 'undefined',
ssrMode: typeof window === 'undefined',
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createClient()
if (initialState) {
const existingCache = _apolloClient.extract()
console.log('existingCache', existingCache);
// _apolloClient.cache.restore({ ...existingCache, ...initialState }) // commented out on purpose
_apolloClient.cache.restore(initialState)
}
if (typeof window === 'undefined') return _apolloClient
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo({ initialState }), [initialState])
return store
}
So after going through my backend source code, i found out that i wasn't returning the updated values from the database but rather the old ones instead. I fixed it and it works as it should.
Related
[I am upgrading my current NextJS site to the latest version of NextJS 13 with the app directory and using generateStaticParams, I keep getting the following error when migrating my getStaticPaths to the new app directory page, I am using GraphQL:
Error: A required parameter (slug) was not provided as a string in generateStaticParams for /[slug]
I've consoled the returns and it seems to be returning the correct object with strings, not sure what I am missing.
// app/[slug]/page.tsx
export async function generateStaticParams() {
const { data } = await client.query({
query: gql`
query getSlugs {
posts(first: 100) {
nodes {
slug
}
}
}
`,
})
const response = data.posts.nodes.map((post: Post) => post.slug)
const slugs = await response.map((slug: string) => ({ params: { slug } }))
return [{ slugs }]
/* returns
[{ params: { slug: 'blog-post-1' } }, { params: { slug: 'blog-post-2' } }...]
*/
}
async function getPost(params) {
const { data } = await client.query({
query: gql`
query singlePost {
post(id: "${params.slug}", idType: SLUG) {
content
categories {
nodes {
name
}
}
featuredImage {
node {
sourceUrl
}
}
postId
}
}
`,
})
return {data.post}
}
export default async function PostPage({ params }) {
const post = await getPost(params)
return <Post post={post} />
}
You must remove the params key inside the object you return, you must only return the slug.
export async function generateStaticParams() {
const { data } = await client.query({
query: gql`
query getSlugs {
posts(first: 100) {
nodes {
slug
}
}
}
`,
})
const response = data.posts.nodes.map((post: Post) => post.slug)
const slugs = response.map((slug: string) => ({ slug }))
return slugs
/* returns
[{ slug: 'blog-post-1' }, { slug: 'blog-post-2' }...]
*/
}
I'm stuck using useInfiniteQuery.
The first call works fine, but the next page is not called with getNextPageParam
const getProductItems = async (par) => {
console.log("axios :", par);
const res = await axios.get(`/api/v1/products`, {
params: par,
});
return {
result: res.data,
};
};
export default function useGetProductItems(params) {
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
["getItems"],
({ pars = params }) => getProductItems(pars),
{
getNextPageParam: (res) => {
console.log(res);
const nextParams = {
...res.result.pageInfo,
page: res.result.pageInfo.page + 1,
};
console.log("next :", nextParams);
return nextParams;
},
select: (data) => {
return data.pages[0].result.data;
},
}
);
return {
data,
isLoading,
fetchNextPage,
hasNextPage,
isFetching,
};
}
And the Query Client setting is like this
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: errorHandler,
}),
mutationCache: new MutationCache({
onError: errorHandler,
}),
});
As I am new to react-query, I am also wondering if there is any data that must be received from the API.
plz answer for me
You can access pageParam and send it as argument to your fetching function. Also it'd be a good idea to check if there really is a next page before incrementing the actual page number in getNextPageParam. Something like this:
const { data, isLoading, fetchNextPage, hasNextPage, isFetching } =
useInfiniteQuery(
['getItems'],
({ pageParam = 1 }) => getProductItems(pageParam), // pageParam defaults to the first page
{
getNextPageParam: lastPage => {
return lastPage.result.pageInfo.page < lastPage.result.pageInfo.totalPages // Here I'm assuming you have access to the total number of pages
? lastPage.result.pageInfo.page + 1
: undefined // If there is not a next page, getNextPageParam will return undefined and the hasNextPage boolean will be set to 'false'
},
select: data => {
return data.pages[0].result.data
},
}
)
I don't have information about how is your API endpoint built, but typically the request should look, for example, like this:
const getProductItems = async (page) => {
const res = await axios.get(`/api/v1/products?page=${page}`);
return {
result: res.data,
};
};
I tried so hard to update Apollo cache after running Mutation, but i couldn't be able to remove 1 second delay after the mutation.
I followed 'ac3-state-management-examples' for solve this problem, but still couldn't find any problem.
This is my client-side code.
export const DELETE_ITEM_IN_CART = gql`
mutation DeleteItemInCart($cartItemId: String!) {
DeleteItemInCart(cartItemId: $cartItemId)
}
`;
export function useDeleteItemInCart() {
console.log(`DELETION START! ${Date()}`);
const [mutate, { data, error }] = useMutation<
DeleteItemInCartType.DeleteItemInCart,
DeleteItemInCartType.DeleteItemInCartVariables
>(DELETE_ITEM_IN_CART, {
update(cache, { data }) {
const deletedCartItemId = data?.DeleteItemInCart;
const existingCartItems = cache.readQuery<myCart>({
query: MY_CART,
});
if (existingCartItems && deletedCartItem && existingCartItems.myCart) {
cache.writeQuery({
query: MY_CART,
data: {
myCart: {
cartItem: existingCartItems.myCart.cartItem.filter(
t => t.id !== deletedCartItemId,
),
},
},
});
console.log(`DELETION OVER! ${Date()}`);
}
},
});
return { mutate, data, error };
}
And here's my server-side mutation
export const DeleteItemInCart = mutationField('DeleteItemInCart', {
args: {cartItemId: nonNull('String')},
type: nonNull('String'),
description: 'Delete an item in my cart',
resolve: (_, {cartItemId}, ctx) => {
const {prisma} = ctx;
try {
prisma.cartItem.delete({
where: {
id: cartItemId,
},
});
return cartItemId;
} catch (error) {
return cartItemId;
}
},
});
This is an example of Apollo-remote-state-mananagement
export const DELETE_TODO = gql`
mutation DeleteTodo ($id: Int!) {
deleteTodo (id: $id) {
success
todo {
id
text
completed
}
error {
... on TodoNotFoundError {
message
}
}
}
}
`
export function useDeleteTodo () {
const [mutate, { data, error }] = useMutation<
DeleteTodoTypes.DeleteTodo,
DeleteTodoTypes.DeleteTodoVariables
>(
DELETE_TODO,
{
update (cache, { data }) {
const deletedTodoId = data?.deleteTodo.todo?.id;
const allTodos = cache.readQuery<GetAllTodos>({
query: GET_ALL_TODOS
});
cache.writeQuery({
query: GET_ALL_TODOS,
data: {
todos: {
edges: allTodos?.todos.edges.filter((t) => t?.node.id !== deletedTodoId)
},
},
});
}
}
)
return { mutate, data, error };
}
Any advice?
1 second delay is inevitable using apollo cache?
I took a short video of my issue. i dont think it's inevitable...
I am new to graphql and react.. Here , I have following method,
export const useName = (isRequest: boolean) => {
const {
nav: { id, idme }
} = stores
if (buyingSession) {
const { data, loading,error}
= usegroup(id, {
fetchPolicy: 'cache-and-network'
})
} else {
const {data} = usesingle(idme)
}
return data;
}
//This function should return a string which will be in data object.But I am getting confused over here because its a query and it takes time so it returns undefined but when I checked in the network it gives response as well.
export const useme = (id: string) => {
return useQuery(GET_ME, {
fetchPolicy: "cache-first",
skip: !id,
variables: {
id: id
}
}
In another component , I am calling ,
const data = useName(true)
So, this is the call which actually calls the graphql query. Now , when I am getting data it gets undefined.
How do resolve this issue ?
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 ?