Issue with refetching specific GraphQL obvervable query - reactjs

I'm very new to React and GraphQL(Apollo) and learning the stack.
I have a React Component that loads some page data and there is a "Reload" button on the page.
Problem:
I want to refetch the data when I click on "Reload" button.
However, I could not find a way to refetch the data from queryObservable.
How do I refetch the data from QueryObservable? Is there anyway to call refetch?
I tried using reFetchObservableQueries() however, this reloads the page and refetches everything. Can I specifically refetch only a single observable?
This is my code:
getPageQueryObservable = (contentId, key, paginated = false) => {
const query = contentId ? TreeQuery : RootLevelQuery;
const defaultVariables = 'Test';
const variablesForPagination = {
paginationLimit: INITIAL_PAGE_SIZE,
...defaultVariables
};
return this.props.apolloClient.watchQuery({
query,
variables: paginated === true ? variablesForPagination : defaultVariables
});
};
loadPages = async (paginated = false) => {
const { pages: localPages } = this.state;
const { id, key } = this.props;
const queryObservable = this.getPageQueryObservable(
id,
key,
paginated
);
// I want to ideally call this stuff again..When "Reload" button is clicked.
this.querySubscription = queryObservable.subscribe({
next: ({ data, loading }) => {
....
});

You can call query with the same query and variables but a fetchPolicy of network-only in order to refetch the query. This should update the cache and therefore any relevant Observables.
await client.query({ query, variables, fetchPolicy: 'network-only' })
However, there's really no reason to use watchQuery directly. Instead, you should use the hook API to fetch and refetch your data.
const { data, loading, refetch } = useQuery(query, { variables })
Not only does this reduce boilerplate, but now refetching is as simple as
await refetch()

Related

React not re-rendering items, when array gets replaced

We are using Next.js + ApolloClient and we have a simple pagination, where, when I click on "2" the next page should be fetched and displayed.
This looks somewhat like this
export default function Reviews ({ rating }) {
const router = useRouter()
const id = router.query.id
const [page, setPage] = useState(1)
const [comments, setComments] = useState(rating.comments) // This is fetched by the server. Initial data
const [fetchComments] = useLazyQuery(GET_RATING_COMMENTS, {
variables: {
id,
page,
first: 3
},
onCompleted: data => {
setComments(data.comments)
}
})
const handleOnPageChange = page => {
setPage(page)
fetchComments()
}
const reviews = comments.map(rating => (
So, it seems like ApolloClient caches the result, which is cool. So the pagination AND rerender works perfectly fine, when the result gets initially fetched per a HTTP Request (not cached) but after ApolloClient has cached the result and I revisit a page that I've clicked before (for instance 2) then reviews is not getting updated.
Which is weird because, onCompleted gets called with the cached result. So I set it but its not updating anymore. The list does not change.
The list only changes initially, when the Page has not been visited yet. If its cached, nothing happens anymore. Why?
Btw, if I add fetchPolicy: 'no-cache' it magically works.

React Query invalidateQueries updating data after post

I'm working on a simple React CRUD with React Query, and I've being stuck for a while trying to update my fetched data after I post a new item with invalidateQueries but it doesn't work, being trying many things like: onSuccess, onMutate, onSettled, etc etc, and nothing seems to work, also following the React Query Documentation. Here is my code:
const queryClient = useQueryClient();
const handleSubmitButtonNew = async (data) => {
api.createAnnouncements(data);
};
const mutation = useMutation(handleSubmitButtonNew, {
onSuccess: () => {
queryClient.invalidateQueries("Announcements");
},
});
const { data, status, isLoading } = useQuery(
"Announcements",
api.getAnnouncements
);
and then in a child component I call:
mutation.mutate({ title: title.value, content: content.value, user });
Can I call the queryClient.invalidateQueries("Announcements"); from another component where the "Announcements" originally is fetching?
What I'm doing wrong ?

refetch in reactQuery is not return the data

I am using reactQuery in my react application. I need to call one get API in button click. for that i am using refetch option in reactQuery. API call is working fine but my response data is coming undefined. I checked in browser network there i can see the response.
My API call using reactQuery
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
My click event
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
My action call using axios
export const getRoles = (name) => axios.get(roles/list?sa_id=${name}, { headers: setHeader }).then(res => res);
const handleAdd = (e) => { roleRefetch(); console.log(roles) }
this not how react works, and it's not react-query specific. calling a function that updates some state will not have your state be available in the next line. It will make it available in the next render cycle. Conceptually, you want this to work, which cannot with how react is designed:
const [state, setState] = React.useState(0)
<button onClick={() => {
setState(1)
console.log(state)
}}
here, the log statement will log 0, not 1, because the update doesn't happen immediately, and this is totally expected.
With react-query, what you can do is await the refetch, because its async, and it will give you the result back:
const handleAdd = async (e) => {
const { data } = await roleRefetch();
console.log(data)
}
or, depending on what you actually want to do, you can:
use data in the render function to render something - it will always be up-to-date.
use theonSuccess callback of useQuery to trigger side-effects whenever data is fetched
spawn a useEffect in the render function that does the logging:
const { data: roles, refetch: roleRefetch } = useQuery('getRoles', () => api.getRoles('ID_234'), { enabled: false });
React.useEffect(() => {
console.log(roles)
}, [roles])
on a more general note, I think disabling a query and then calling refetch on a button click is very likely not idiomatic react-query. Usually, you have some local state that drives the query. in your case, that's likely the id. Dependencies of the query should go to the queryKey, and react-query will trigger a refetch automatically when the key changes. This will also give you caching by id. You can use enabled to defer querying when your dependencies are not yet ready. Here's what I would likely do:
const [id, setId] = React.useState(undefined)
const { data: roles } = useQuery(['getRoles', id], () => api.getRoles(id), { enabled: !!id });
const handleAdd = (e) => { setId('ID_234') }
of course, id doesn't have to come from local state - it could be some other form of client state as well, e.g. a more global one.

react apollo - clear data object before refetch

When using refetch of useQuery hook the data object is still defined. And when using infinite scrolling, only the first page will be refetched.
Is it possible to clear the data object before calling the refech so that we can start fresh?
const { data, loading, error, fetchMore, refetch } = useQuery(GET_ALL_ITEMS, {variables});
getNextPage = async () => { // merges results for infinite scrolling
await fetchMore({ variables,
updateQuery: (previousResult, { fetchMoreResult }) => {
const oldEntries = previousResult.items;
const newEntries = fetchMoreResult.items;
fetchMoreResult.items = [...oldEntries, ...newEntries];
return fetchMoreResult;
},
)
}
Can I do something like refresh = () => { data = null; refetch(); } but without directly mutating state?
I couldn't find a way to clear data but found a different approach in Apollo Client v3 docs.
Basically it's to ignore loaded data when loading or in case of error:
const { data: loadedData, loading, error, fetchMore, refetch } = useQuery(
GET_ALL_ITEMS,
{
variables,
notifyOnNetworkStatusChange: true, // important for adequate loading state
}
);
const data = (loading || error) ? undefined : loadedData;
Note, that in order to make loading work for refetch, you need to pass notifyOnNetworkStatusChange: true option.
It's also possible to get networkStatus from useQuery result and check it like this:
networkStatus === NetworkStatus.refetch
but it seems to work for me with loading and error only.

Unexpected result from apollo react's useQuery method

For a project, where i've implemented authentication by running a GraphQL query inside a AuthenticationProvider from a context, I noticed the query is fetching data twice.
const AuthenticationProvider: FC = props => {
const {
loading,
data
} = useQuery(MeQuery)
if (loading) return null
return <AuthenticationContext.Provider value={{user: data?.me || null}} {...props} />
}
However the query runs perfect, I still wanted to know why it fetches the data twice. I did some googling, and came across this issue, where this answer was provided. I tried the same thing, with the skip option, based if the data is loaded.
const [skip, setSkip] = useState(false)
const {
loading,
data
} = useQuery(MeQuery, { skip })
useEffect(() => {
if (!loading && data?.me) {
setSkip(true)
}
}, [loading, data])
// ...
But when logging in, it stopped working.
const useLoginMutation = () => useMutation(LOGIN_QUERY, { update: (cache, { data }) => {
if (!data) {
return null
}
cache.writeQuery({ query: MeQuery, data: { me: data.login } })
}
})
The cache still get's updated with the right values, but doesn't retrieve the user anymore (null).
const { user } = useContext(AuthenticationContext)
What am I missing here? It seems the query did run and fetched the correct data.
You don't need to use context when you are using apollo useQuery. If you make a query first, then the data fetched will be stored in the cache. You can directly access the data from the cache for the second you run the query. Since useQuery has default fetchPolicy cache-first. Mean its check in the cache first, if no query made before it makes a network request.
If you want to avoid network loading. You can make a top-level component AuthWrapper.
const useUserQuery = () => useQuery(ME_QUERY);
const AuthWrapper = ({children}) => {
const {loading, data} = useUserQuery();
if(loading) return null
return children;
}
export GetUsetInThisComponent = ({}) => {
// Since we have fetched the user in AuthWrapper, the user will be fetched from the cache.
const {data} = useUserQuery();
// No you can access user from data?.user
// Rest of the application logic
}
// Wrap the component like this to avoid loading in the children components
<AuthWrapper>
<GetUserInThisComponent />
</AuthWrapper>

Resources