skip to Main Content

I am currently trying to implement Redux-Toolkit Query for the first time and am facing issue in delete item implementation. As mentioned by other people on this topic, I also face the issue of get item query being called again right after delete item is called. Here are my code snippets

getAllSchedules: builder.query({
  query: (params) => ({
    url: '/schedules/',
    params,
  }),
  transformResponse: (response) => ({
    schedules: response.results,
    pagination: {
      count: response.count,
      current_limit: response.limit,
      total_pages: response.total_pages,
      current_page: response.current_page,
    },
  }),
  providesTags: (result) =>
    Array.isArray(result?.schedules)
      ? [
        ...result.schedules.map(({ id }) => ({
          type: 'Schedule',
          id,
        })),
        'Schedule',
      ] : ['Schedule'],
    }),
getScheduleById: builder.query({
  query: (id) => `/schedules/${id}/`,
  providesTags: (result, error, id) => [{ type: 'Schedule', id }],
}),
deleteSchedule: builder.mutation({
  query: (id) => ({
    url: `/schedules/${id}/`,
    method: 'DELETE',
  }),
  invalidatesTags: (result, error, id) => [{ type: 'Schedule', id }],
}),

getAllSchedules and getScheduleById are used to get schedule data list and individual schedule data item respectively, while the last function is used to delete a single schedule item. I am doing the delete schedule operation inside a table listing, so want the data to be updated right after delete, but invalidating cache like this causes a getScheduleById call which does not exists in BE anymore and thus creating a 404 error.

I don’t want this error to occur because it is shown to the user in the system because of default error handling in the site. But at the same time I can’t do manual cache invalidation like this

onQueryStarted: async (id, { dispatch, queryFulfilled }) => {
  try {
    const patchResult = dispatch(
      scheduleApiSlice.updateCachedData(
        'getAllSchedules',
        undefined,
        (draft) => {
          draft.schedules = draft.schedules.filter(
            (schedule) => schedule.id != id
          );
        }
      )
    );
    await queryFulfilled;
  } catch {
    patchResult.undo();
  }
},

because this requires me to pass all params to the getAllSchedules query same as before and as I am using pagination and searching for my listing, I have to supply all the params related to it in this updateCache, which I can’t do right now because I don’t have access to it in handleDelete and I don’t want to over complicate stuff by sharing the variables from listing to handleDelete and stuff. Is there a nice way to handle this?

2

Answers


  1. The deleteSchedule function should invalidate the entire list. Use getAllSchedules to provide only the [‘Schedule’] tag without any IDs. Upon deletion, invalidate the [‘Schedule’] tag. This approach ensures that only getAllSchedules will re-trigger when an item is deleted.

    deleteSchedule: builder.mutation({
        query: (id) => ({
            url: `/schedules/${id}/`,
            method: 'DELETE',
        }),
        invalidatesTags: ['Schedule'], 
    }),
    
    getAllSchedules: builder.query({
        ...
        providesTags: ['Schedule']
    }),
    
    Login or Signup to reply.
  2. Issue

    Updating the cached data, i.e. scheduleApiSlice.updateCachedData, is not the same as invalidating query cache tags.

    What you are doing with onQueryStarted is more akin to optimistic updates where you are deleting a schedule and optimistically removing it from the current cached data client-side. This is a mechanism you’d use in place of tag validation, e.g. not using cache tags and instead manually managing all the cached data yourself, and saving any re-fetches. But as you see though, you need to manage it all.

    By including the id in the deleteSchedule endpoint’s invalidatesTags value you are informing RTKQ to re-trigger all queries that provide that same tag, e.g. the getScheduleById query endpoint. Since you just deleted that document in the backend it doesn’t make sense to trigger the frontend to re-fetch it.

    Solution Suggestion

    Remove id from the deleteSchedule endpoint’s invalidatesTags array. Include only the "Schedule" tag value to trigger revalidating the getAllSchedules query endpoint.

    getAllSchedules: builder.query({
      query: (params) => ({
        url: '/schedules/',
        params,
      }),
      ...,
      providesTags: (result) =>
        Array.isArray(result?.schedules)
          ? [
            ...result.schedules.map(({ id }) => ({
              type: 'Schedule',
              id,
            })),
            'Schedule',     // <-- "schedule"
          ] : ['Schedule'], // <-- "schedule"
    }),
    getScheduleById: builder.query({
      query: (id) => `/schedules/${id}/`,
      providesTags: (result, error, id) => [{ type: 'Schedule', id }],
    }),
    deleteSchedule: builder.mutation({
      query: (id) => ({
        url: `/schedules/${id}/`,
        method: 'DELETE',
      }),
      invalidatesTags: ['Schedule'], // <-- "schedule"
    }),
    

    If you were doing just about anything other than deleting a schedule, like just updating it, this is when you’d include the id value so you can trigger re-fetching just that specific schedule.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search