Using apollo subscribeToMore with different results for subscription - reactjs

GET_USERS returns users: []string
export const GET_USERS= gql`
query AllUsers{
users
}
`;
GET_USERS_SUBSCRIPTION returns user: string
export const GET_USERS_SUBSCRIPTION= gql`
subscription AllUsersUpdater{
user
}
`;
It seems like apollo expects the subscribeToMore to be of the exact same result type. I know I can update the result to also be []string, but that seems so unnecessary for what I want to do.
const { subscribeToMore } = useAllUsersQuery({});
useEffect(() => {
subscribeToMore({
document: GET_USERS_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const user= subscriptionData.data.user;
if (prev.users.find((u) => u === user)) {
return prev;
}
return Object.assign({}, prev, {
users: [user, ...prev.users],
});
},
});
}, []);
Is there any way to use subscribeToMore with a different subscription type?
Also, is subscribeToMore the best way to handle this? Could I just bundle the two and just have the subscription return past results prior to returning new ones?

The subscribeToMore as defined here https://github.com/apollographql/apollo-client/blob/a3aefcb310ea1451ee494077cbde98171169d8d0/src/core/ObservableQuery.ts#L502-L505 used the TSubscriptionData = TData type parameter to set the expected type which defaults to TData.
You need to specify the parameter to override the default.
In your specific example this would look something like this
const { subscribeToMore } = useAllUsersQuery({});
useEffect(() => {
subscribeToMore<{user: string}>({
document: GET_USERS_SUBSCRIPTION,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const user= subscriptionData.data.user;
if (prev.users.find((u) => u === user)) {
return prev;
}
return Object.assign({}, prev, {
users: [user, ...prev.users],
});
},
});
}, []);

Related

Is there a way for react-query to revalidate cached data in case server response is OK but does not contain actual data?

I'm exploring react-query but couldn't find an example for one particular use case.
A demo todo API server returns the following JSON for getTodos(ifModifiedSince=1234567) request:
In case todo list not modified since specified time:
{"status":"not_modified"}
Otherwise:
{"status":"ok","todos":[{"userId":1,"id":2,"title":"TodoItem1","completed":false}],"lastModified":"1234589"}
The closest I came up with:
const { isLoading, isError, data } = useQuery(
[todosQueryKey],
() => {
const lastModified = data?.lastModified || '';
const result = getTodos({
ifModifiedSince: lastModified,
});
return result;
},
{
select: (newData): GetTodosResponse | undefined => {
let resolvedData = newData;
if (newData.status === StatusNotModified) {
//! This actually does not work because getQueryData
//! already returns data from most recent 'not_modified' request.
resolvedData = queryClient.getQueryData([todosQueryKey]) || newData;
}
resolvedData.todos?.sort((a: TodoItem, b: TodoItem) => b.id - a.id);
return resolvedData;
},
refetchInterval: 5000,
staleTime: Infinity,
keepPreviousData: true,
}
);
What is the correct way to do the above mentioned task with react-query?
My current solution is to use useQuery() as usual but instead modify getTodos() function
so that it receives cached data as an optional second parameter and
returns it if status==='not_modified':
export const getTodos = async (
req: GetTodosRequest,
currentData?: GetTodosResponse | undefined
): Promise<GetTodosResponse> => {
return todosApi
.get(`/todos/`, {
params: req,
})
.then((resp) => {
const newData = validateResponse(resp);
if (newData.status === StatusNotModified) {
// If server responded with StatusNotModified,
// return cached data if any.
return currentData || newData;
}
return newData;
});
};
const { isLoading, isError, data } = useQuery(
[todosQueryKey],
() => {
const lastModified = data?.lastModified || '';
const result = getTodos(
{
ifModifiedSince: lastModified,
},
data
);
return result;
},
{
select: (newData): GetTodosResponse => {
newData.todos?.sort((a: TodoItem, b: TodoItem) => b.id - a.id);
return newData;
},
onSuccess: () => {
setWaitingForResponse(false);
},
refetchInterval: 5000,
staleTime: Infinity,
}
);

HTTP put and get(id) request ReactQuery

I change the redux in my project to ReactQuery,and i got some problem with put req in my code.
this is my code
const { dispatch } = store;
const editClientDataAsync = async ( id,data ) => {
await axiosObj().put(`clients/${id}`,data);
}
const { mutateAsync: editClientData, isLoading } = useMutation(['editClientData'], editClientDataAsync, {
onSuccess: () => dispatch({ type: SUCCESS_DATA, payload: { message: "Success" } }),
onError: () => dispatch({ type: ERROR_DATA, payload: { message: "Error" } })
});
return { editClientData, isLoading }
}
same problem with when i try to get some req with id
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async ({ queryKey }) => {
const [_, { id }] = queryKey;
console.log(queryKey)
if (!id)
return;
const { data } = await axiosObj().get(`clients/${id}`)
console.log(data)
return data;
}
const { data: clientDetails, isLoading } = useQuery(['ClientId', { id }], getClientDetails)
return { clientDetails, isLoading }
Mutation functions only take 1 argument
Check where you use the editClientData mutation and pass the arguments in one object.
const editClientDataAsync = async ({ id, data }) => {
await axiosObj().put(`clients/${id}`,data);
}
return useMutation(['editClientData'], editClientDataAsync, ...);
Are you sure you get an id passed to the function?
You can disable the query until you get that id with the enabled option, so you don't make an unnecessary http call.
const id = useSelector((state) => state?.clientData?.clientInfo?.data.id)
const getClientDetails = async (id) => {
const { data } = await axiosObj().get(`clients/${id}`)
return data;
}
return useQuery(['client', id], () => getClientDetails(id), { enabled: !!id })
Disable/pausing queries

React-query useInfiniteQuery: getNextPageParam not working

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,
};
};

Apollo client fetchMore does not update variables

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.

Query data doesn't update after successful apollo cache write

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 },
});
},
});
}

Resources