I have used a query in this manner:
const { data, refetch, loading, fetchMore } = useQuery<{ personComments: PersonCommentConnection }>(
PERSON_GET_COMMENT,
{
fetchPolicy,
variables,
skip: !isBrowser,
onCompleted: () => setInitializedQuery(true),
}
)
and a fetch more function
const fetchMorePersonComments = async () => {
setFetchingMore(true)
await fetchMore({
variables: {
after: data?.personComments.pageInfo.endCursor,
},
updateQuery: (
previousEntries,
{ fetchMoreResult, variables }
): { personComments: PersonCommentConnection } => {
if (!fetchMoreResult) {
return previousEntries
}
if (fetchMoreResult) {
fetchMoreResult.personComments.edges = [
...previousEntries.personComments.edges,
...fetchMoreResult.personComments.edges,
]
}
return fetchMoreResult
},
})
setFetchingMore(false)
}
I used infinite scrolling to call fetchmore comments but when it's been called It only refetches the first query instead of adding the after query.
Note I've successfully created queries similar to this one but only fails for this
Do you have any ideas why during in the fetchmore query it does not update the query variables.
Related
I have a useQuery which is disabled in a react function component. I have another useQuery that uses mutate and on the success it calls refetchMovies(). This all seems to work well but I'm seeing old data in the refetchMovies. Is there a way for to get the refetchMovies to always call fresh data from the server when its called ?
const MyComponent = () => {
const {data, refetch: refetchMovies} = useQuery('movies', fetchMovies, {
query: {
enabled: false
}
})
const {mutate} = useQuery('list', fetchList)
const addList = useCallback(
(data) => {
mutate(
{
data: {
collection: data,
},
},
{
onSuccess: () => refetchMovies(),
onError: (error) => console.log('error')
}
)
},
[mutate, refetchMovies]
)
return (
<div onClick={addList}> {data} </div>
)
}
Try to invalidate the query in your onSuccess callback instead of manually refetching it:
https://tanstack.com/query/v4/docs/react/guides/query-invalidation
Example:
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })
I have a hook (useDashboardData) that calls another hook (useItems) that's just a wrapper for two Apollo client queries.
inside the first hook useDashboardData, i'm also calling another hook useOtherItems that also calls another Apollo client query.
export const useDashboardData = () => {
const { item, isItemsLoading } = useItems();
const { list, isOtherItemsLoading } = useOtherItems();
const dashboardData = {
items: {
itemsLoading: isItemsLoading,
itemsData: item,
},
otherItems: {
otherItemsLoading: isOtherItemsLoading,
otherItemsData: list,
},
};
return {
dashboardProps: {
dashboardData: dashboardData,
},
};
};
useItems.tsx
export const useItems = () => {
const { user } = useAuthorization();
const {
data: itemData,
loading: items Loading,
} = useCustomApolloGetItemsQuery({
skip: !user.id,
variables: { user.id },
});
const {
data: moreItemData,
loading: moreItemsLoading,
} = useAnotherApolloGetItemsQuery({
skip: !user.id,
variables: { user.id },
});
const combinedItems = combineItemData(itemData, moreItemData);
return { combinedItems, ItemsLoading };
useOtherItems.tsx
export const useOtherItems = () => {
const { user } = useAuthorization();
const { data: list, loading: isOtherItemsLoading } = useGetInvoiceListQuery({
skip: !user.id,
variables: {
userId: user.id,
},
});
return { list, isOtherItemsLoading };
For some reason, anytime I introduce the second hook, the previous requests get canceled. which one is arbitrary but it's consistently canceled.
I'm pretty sure it's due to the first hook request resolving earlier and causing a re-render before the request in the second hook is resolved.
I need to figure out the right pattern to deal with this.
**note I have made sure the Apollo Client is only instantiated once so it's not that.
I am receiving data from an api call, taking that data and restructuring it to properly display in a table. When a user clicks a button I am trying to create a copy of that record. I've got it all working, its just not updating the table with the appended, or removed (for delete) data. until after i refresh the page through the browser.
Is it possible to call a function after refetchQueries?
const {
loading: appLoading,
data: applicationsData,
refetch: refetchApplicationsData,
} = useQuery(applications.operations.GET_APPLICATIONS_BY_COMPANY, {
client: applications.client,
variables: { companyId: userDetails.companyId },
})
const [
CloneApplication,
{ loading: cloneLoading, data: cloneData, error: cloneError },
] = useMutation(applications.operations.CLONE_APPLICATION_BY_COMPANY, {
client: applications.client,
onCompleted: () => {
refetchApplicationsData
},
})
useEffect(() => {
if (applicationsData && templatesList) {
const newFinalData = getFinalData({
applicationsList: applicationsData.getApplicationsByCompany,
templatesList: templatesList,
})
console.log('oldFinalData: ', finalData)
console.log('newFinalData: ', newFinalData)
setFinalData(newFinalData)
console.log('updatedFinalData: ', finalData)
}
}, [applicationsData, templatesList])
const cloneAndRefresh = (applicationId, companyId, ucId) => {
CloneApplication({
variables: {
applicationId: applicationId,
companyId: companyId,
ucId: ucId,
},
}).then(({ data: responseData }) => {
if (responseData) {
console.log('response data: ', responseData)
console.log('applications: ', applicationsData)
}
})
}
the function to restructure data:
export function getFinalData(request: {
templatesList: GetAllTemplate[]
applicationsList: GetApplicationsByCompany[]
}): FinalDataResponse[] {
const templates = request.templatesList.map((template) => {
const applicationsForTemplate = request.applicationsList.filter(
(app) => app.templateId === template.templateId
)
return { ...template, applications: applicationsForTemplate }
})
const groupedData = _.chain(templates)
.groupBy('templateId')
.map((value, key) => {
const templateName = _.chain(value)
.groupBy('templateName')
.map((value, key) => key)
.value()
const createdDate = _.chain(value)
.groupBy('dateCreated')
.map((value, key) => dayjs(key).format('ll'))
.value()
const lastModified = _.chain(value)
.groupBy('lastModified')
.map((value, key) => dayjs(key).format('ll'))
.value()
return {
templateId: key,
templateName: templateName[0],
createdDate: createdDate[0],
lastModified: lastModified[0],
applications: value[0].applications,
}
})
.value()
const finalData = groupedData.map((object, index) => {
return {
...object,
totalApplications: object.applications.length,
}
})
console.log('returning final data: ', finalData)
return finalData
}
I guess im trying to rerun getFinalData after the refetchquery then save it to state and it should re-render the table?
EDIT: I've updated my queries with new code, though it didnt quite work. If its possible to get the data from the refetched query I think i could make it work. I assume that refetching the query would update applicationsData as a result but i dont think it did?
By default, the useQuery hook checks the Apollo Client cache to see if all the data you requested is already available locally. If all data is available locally, useQuery returns that data and doesn't query your GraphQL server. This cache-first policy is Apollo Client's default fetch policy. If you say that you will call handleRefresh() after mutation the below code will work fine.
here read fetch policy
const {
loading: appLoading,
data: applicationsData,
refetch: refetchApplicationsData,
} = useQuery(applications.operations.GET_APPLICATIONS_BY_COMPANY, {
client: applications.client,
variables: { companyId: userDetails.companyId },
fetchPolicy: "network-only",
})
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 using react-query to call an API. The call works well and is performed each time a query value is updated in an input field.
Unfortunately, it also triggers an API call even when the query is empty.
For example, when the user loads the app, the input (and hence query) will be blank.
How to trigger API calls only when there is a query?
Code
// API call
export async function myQuery(query) {
try {
const res = await ax.get("myapiurl", {
params: { query },
});
return res.data;
} catch {
return null;
}
}
// react-query
const { status, data } = useQuery(
["myquery", { query }],
() => myQuery(query)
);
There is an enabled flag in react-query for this exact use case.
Usage example
const { status, data } = useQuery(
["myquery", { query }],
() => myQuery(query).
{ enabled: !!query }
);
Docs for reference
You can achieve that with a simple if sentence:
// apicall
export async function myQuery(query) {
try {
const res = await ax.get("myapiurl", {
params: { query },
});
return res.data;
} catch {
return null;
}
}
// react-query
const { status, data } = useQuery(
["myquery", { query }],
() => {
if (query) {
return myQuery(query)
}
);