RTK Query sustain `isLoading` for auto-refetching after cache invalidation - reactjs

Having a hard time finding a good answer/ solution for this problem.
I have a POSTS list component that allows to delete individual POST rows.
I'm using a run of the mill queryMutation for deletion purposes:
IMPLEMENTATION
deletePostById: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
})
USAGE
const [deletePost, { isLoading: isDeleting, error: deletionError }] = useDeletePostByIdMutation();
PROBLEM DESCRIPTION
Within the list component the individual row has an icon to delete that Post - while isDeleting I'm showing a spinner, which is also showing just fine - however, when the Post is deleted the auto-refetching of RTKQ kicks in to get the now updated Posts from the server - isDeleting is no longer true though.
This leaves a small time window in which the Post row is not showing any spinner but also is not removed yet from the Posts list.
Once the refetched data of all Posts has successfully returned the deleted Post row gets removed from the list.
How can I sustain the spinner animation from deleting the individual Post till the removal after the automatic refetching of RTKQ has finished?
Thanks

You can use isFetching on the list query instead of isLoading, which is only true for the initial request, not refetches.

Related

How do I fix "Text content does not match server-rendered HTML" in nextjs/typescript

I am working on a nextjs/typescript project that helps users to create a list of items they want to purchase.
When a user has purchased an items, they come to the application and tick that it has been purchased. Whenever this happens, I have to update the cart on my database (firestore)
The problem is that the code I use to update the database gives me the following error on the browser:
picture of error on browser
Below is my code when the user clicks on the item.
async function toggleItemIsCompletedState() {
dispatch(
cartActions.toggleIsCompletedState({
itemId,
categoryName: props.category,
})
);
// update cart in firebase
// ================================================
// For some reason I am not able to get the updated data to push to firestore so I am going to duplicate
// the code for updating the redux-store here so I can get updated data before pushing it.
const updatedItems = toggleItemIsCompletedStateUtil(
items,
props.category,
itemId
);
if (updatedItems === false) return;
console.log(updatedItems);
const cartData: CurrentCartUploadType = {
cartTitle,
items: updatedItems,
totalQuantity,
cartState,
isEditingCart,
};
// console.log("CartData: ", cartData);
const response = await updateCart(cartData);
console.log(response);
// ================================================
}
I viewed the docs and the solution was to use Effect but there are so many dependencies that the program would always keep re-rendering besides I tried using useEffect() but I kept getting the same error.
You can find my code on GitHub: https://github.com/almamarie/shoppingify-frontend in the backend-integration branch.
The target file is components/cart/completing-state/CompletingCarItem.tsx

Order of cache items after update mutation using Apollo Client

I have a question concerning Apollo Client's behaviour when dispatching an update mutation.
I have a little application that fetches data and allows you to modify it. After the modification, an update mutation is sent to graphQL. The changes can be seen instantly on the UI since the update of a single item triggers an automatic cache update by Apollo.
However, I noticed that when I refresh the page after an update, the order of the items I previously fetched is changed with the recently updated item going at the end of the list.
I was just wondering if this is the normal behaviour to expect and if there was a way to force the cache to keep the same order after an update?
Edit: Here's the code for my resolver, mutation and useMutation call.
Resolver:
async UpdateUser(parent, args, ctx, info) {
const { id, input } = args;
const updatedUser = await ctx.prisma.user.update({
where: {
id,
},
data: {
...input,
},
});
return updatedUser;
}
Mutation:
export const UPDATE_USER_MUTATION = gql`
mutation UpdateUser($id: String, $input: CreateUserInput) {
UpdateUser(id: $id, input: $input) {
id
name
email
}
}
`;
useMutation:
UpdateField({
variables: {
id: data.fieldID,
input: {
[data.fieldName]: value,
},
},
});
Edit 2: Here's a gif what's going on..
Thank you!
Mutation update option can update (or insert) properly list/array (query cached result) following BE sorting ...
... but it will fail on longer datasets, paginated results - on list query refetch record can be removed from current [page] list/array. It will be more confusing behavior.
IMHO fighting is not worth the effort, it's already acceptable behavior (mutation of indexed/ordering field).
Possible solutions:
don't refetch list query, only update the query list cache (mutation update - it will update the list view);
for small datasets, use FE sortable table component (BE order doesn't matter).

GraphQL Automatic refetch on empty responses

I want to randomize movies from theMovieDB API. First I send a request to access the ID of the latest entry:
const { loading: loadingLatest, error: errorLatest, data: latestData, refetch: refetchLatest } = useQuery(
LATEST_MOVIE_QUERY
);
Then I want to fetch data from a randomly selected ID between 1 and the number of the latest id. Using a variable dependant on the first query seems to break the app, so for now I'm just using the same movie every time upon mounting the component:
const [
movieState,
setMovieState
] = useState(120);
const { loading, error, data, refetch } = useQuery(ONE_MOVIE_BY_ID_QUERY, {
variables : { movieId: movieState },
skip : !latestData
});
I want to press a button to fetch a new random movie, but the problem is that many of the IDs in the API lead to deleted entries and then I get an error back. I want to keep refetching until I get a good response back but I have no idea to implement it. Right now my randomize function just looks like this:
const randomizeClick = () => {
let mostRecentID = latestData.latestMovie.id;
setMovieState(Math.floor(Math.random() * mostRecentID));
};
I'd be grateful if someone can help me how to implement this.
I think what you needs is the "useLazyQuery" functionality of Apollo. You can find more information about it here: https://www.apollographql.com/docs/react/data/queries/#executing-queries-manually
With useLazyQuery you can change your variables easily and this is meant to be fired after a certain event (click or something similar). The useQuery functionality will be loaded when the component is mounted.

Apollo Client - fetchMore component update problem

In my NextJS application interfaced with a MongoDB API back-end - managed via GraphQL, I'm trying to implement the Apollo fetchMore feature, in order to update a component responsible to load more items from a data collection.
On page rendering, the component itself shows a "gallery" of 10 elements as its native functionality, populated via a GraphQL starting query. Then, I included a "load more" button to trigger the fetchMore function. The UX expects that if the user clicks the proper button, more 10 elements will going to be loaded in addition of the previous 10 - basically a classical async-infinite loading example.
By inspecting the app, I notice that both the queries are being returned successfully - the initialization one and the "load more 10 items" too managed by fetchMore - but the latter, after its execution, triggers the component's update that it's being re-initialized with the starter query instead of the fetchMore one.
To clarify it: on "load more" click, instead to see the next 10 gallery elements loaded - so to finally display a total of 20 - the component refreshes and displays the starter 10 elements, like its starting initialization - totally ignoring the fetchMore action, even if this one is being called, executed and received back with a populated 200 response.
Because this is my very first time in using it, I don't know if I'm missing something in my implementation, or I need to fix something. Anyway, here it goes:
Due to various reasons, I'm running the query in a parent component, then I pass the data as props to a child one:
Parent
// Initialization, etc.
[...]
const {loading: loadingIndex, error: errorIndex, data: dataIndex, fetchMore: fetchMoreIndex} = useQuery(ARTICLE_QUERY.articles.indexArticles, {
// Last 30 days
variables: {
live: live,
limit: 10
}
});
// Exception check
if (errorIndex) {
return <ErrorDb error={errorIndex} />
}
// DB fetching check
if (loadingIndex) {
return (
<section className="index-articles">
<h6>Index - Articles</h6>
<aside className="articles__loading">
<h6>Loading</h6>
</aside>
</section>
);
}
const articles = dataIndex.queryArticleContents;
return (
<IndexArticles labels={props.labels} articles={articles} fetchMore={fetchMoreIndex} />
);
Child
// Initialization, etc.
[...]
let limit = 10; // My query hypothetically limiter
const IndexArticles = (props) => {
useEffect(() => {
// This is a getter method responsible to manage the ```fetchMore``` response
getArticles(props.articles, props.fetchMore);
});
return (
<>
// Component sections
[...]
// Load button
{props.fetchMore &&
<button className="articles__load" title={props.labels.index.title} tabIndex={40}>{props.labels.index.cta}</button>
}
</>
);
function getArticles(articles, fetchMore) {
// Yes, I'm using jQuery with React. Just ignore it
$('.articles__load').on('click tap', function(e) {
e.preventDefault();
$(this).addClass('hidden');
$('.articles__loading').removeClass('hidden');
fetchMore({
variables: {
// Cursor is being pointed to the last available element of the current collection
lastLoaded: articles.length,
limit: limit += 10
},
updateQuery: (prev, {fetchMoreResult, ...rest}) => {
$('.articles__loading').addClass('hidden');
$(this).removeClass('hidden');
if (!fetchMoreResult) {
return prev;
}
return {
...fetchMoreResult,
queryArticleContents: [
...prev.queryArticleContents,
...fetchMoreResult.queryArticleContents
]
}
}
});
});
}
Anyone have experience with it or had experienced this case before?
Thanks in advance for the help
As suggested on the official community, my configuration was missing about the notifyOnNetworkStatusChange: true in the query options, which is responsible to update the component and append the new data.
By changing the code in this way:
Parent
const {
loading: loadingIndex,
error: errorIndex,
data: dataIndex,
// Add networkStatus property too in order to use notifyOnNetworkStatusChange properly
networkStatus: networkStatusIndex
fetchMore: fetchMoreIndex} = useQuery(ARTICLE_QUERY.articles.indexArticles, {
// Last 30 days
variables: {
live: live,
limit: 10
},
// Important for component refreshing with new data
notifyOnNetworkStatusChange: true
});
The problem has been solved.

How to execute query on every click using useLazyQuery()

Using useLazyQuery() hooks from #apollo/react-hooks I was able to execute a query on click of a button. But I cannot use it execute same query on consecutive clicks.
export default ({ queryVariables }) => {
const [sendQuery, { data, loading }] = useLazyQuery(GET_DIRECTION, {
variables: queryVariables
});
return <div onClick={sendQuery}>Click here</div>;
}
In the above the sendQuery executes only once.
useLazyQuery uses the default network policy that of cache-first So I supposed your onClick function actually executes but because the returned value is what was in the cache, React notices no change in data as such the state is not updated since the returned data is what it already has that way no re-render and thing seem not to have changed. I suggest you should pass in a different network policy something like
const [sendQuery, { data, loading }] = useLazyQuery(GET_DIRECTION, {
variables: queryVariables,
fetchPolicy: "network-only"
});
This will mean you want the most recent information from your api hence basically no caching.
You might also want to experiment on other option and see which one best suits you
like cache-and-network: you can find out a little more here understanding-apollo-fetch-policies
As per the docs, useLazyQuery accepts the parameter fetchPolicy.
const [lazyQuery, { data, loading }] = useLazyQuery(QUERY, {
fetchPolicy: 'no-cache'
});
With fetchPolicy set to no-cache, the query's result is not stored in the cache.

Resources