skip to Main Content

Following Fullstackopen course, I’m on Part 6 and learning about useQUery / useMutation.

I got stuck near the end because my app won’t re-render after I call the api and make changes to the list of items. I tried fetchPolicy:'false' in the query params.

The other logic works, I got to add an item and update it but I can see in the network tab of the dev tools that when I fire the update I got the ‘PUT’ request but not followed by a ‘GET’ so I guess the rendering that should be triggered by ‘onSuccess’ does not fire.

I can’t see why and I digged on the internet and here for some use case but couldn’t figure it out.

Thanks yall !

Here is App.js:

import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query'
import { getAnecdotes, updateAnecdote } from './request'

import AnecdoteForm from './components/AnecdoteForm'
import Notification from './components/Notification'

const App = () => {

  const queryClient = useQueryClient()

  const updateAnecdoteMutation = useMutation({
    mutationFn: updateAnecdote,
    onSuccess: (updatedAnecdote) => {
      queryClient.setQueryData(['anecdotes', updatedAnecdote.id], updatedAnecdote)
    }
  })

  const handleVote = (anecdote) => {
    updateAnecdoteMutation.mutate({ ...anecdote, votes: anecdote.votes + 1 })
  }

  const result = useQuery({
    queryKey: ['anecdotes'],
    queryFn: getAnecdotes,
    refetchOnWindowFocus: false
  })

  if (result.isLoading) {
    return <div>loading data...</div>
  }
  if (result.isError) {
    return <div>Error: {result.error.message}</div>
  }

  const anecdotes = result.data

return (...)

AnecdoteForm.js :

import { createAnecdote } from "../request"


const AnecdoteForm = () => {

  const queryClient = useQueryClient()

  const newAnecdoteMutation = useMutation({
    mutationFn: createAnecdote,
    onSuccess: (newAnecdote) => {
      const anecdotes = queryClient.getQueryData(['anecdotes'])
      queryClient.setQueryData(['anecdotes'], anecdotes.concat(newAnecdote))
    }
  })

  const onCreate = (event) => {
    event.preventDefault()
    const content = event.target.anecdote.value
    event.target.anecdote.value = ''
    console.log('new anecdote')
    newAnecdoteMutation.mutate({ content, votes: 0 })
  }
return (...)

request.js :


const baseUrl = 'http://localhost:3001/anecdotes'

export const getAnecdotes = () =>
    axios.get(baseUrl).then(res => res.data)

export const createAnecdote = (newAnecdote) => {
    axios.post(baseUrl, newAnecdote).then(res => res.data)
}

export const updateAnecdote = (updatedAnecdote) => {
    axios.put(`${baseUrl}/${updatedAnecdote.id}`, updatedAnecdote).then(res => res.data)
}

2

Answers


  1. Analysis of the Issue:

    1. Cache Update:

    React-query is what you’re using to handle your caching and data fetching.
    You are using queryClient.setQueryData to refresh the cached data for that particular anecdote after modifying it using updateAnecdoteMutation.
    Your list might not be re-rendering since you are only updating the cache for the specific anecdote and not the complete collection of anecdotes.

    2. Absence of Auto Refetch:

    You reported that a ‘PUT’ request is displayed on the network tab, but no ‘GET’ request is displayed after it.
    This suggests that there isn’t an automatic refetch occurring following the mutation, most likely because queryClient.setQueryData doesn’t initiate a refetch; instead, it merely updates the cache locally.

    Resolutions:

    First solution: Refetch the query and invalidate it

    To make the whole list of anecdotes re-fetch, use queryClient.invalidateQueries. This will guarantee that, following an update, the component re-renders with the updated data and that the most recent data is received from the server.

    To update an anecdote, update your onSuccess method in the useMutation hook:

    const updateAnecdoteMutation = useMutation({
      mutationFn: updateAnecdote,
      onSuccess: () => {
        queryClient.invalidateQueries(['anecdotes'])
      }
    })
    

    By doing this, you can make sure the data is current by invalidating the anecdotes query and forcing it to reload.

    Second Solution: Manually Update the List Cache

    Make sure you are updating the complete list in the cache, not just the specific anecdote, if you wish to manually update the list cache instead of refetching. Although this method prevents further network queries, it necessitates manual cache updates.

    To change the complete list of anecdotes, update the onSuccess method:

    const updateAnecdoteMutation = useMutation({
      mutationFn: updateAnecdote,
      onSuccess: (updatedAnecdote) => {
        queryClient.setQueryData(['anecdotes'], (oldData) => {
          return oldData.map(anecdote =>
            anecdote.id === updatedAnecdote.id ? updatedAnecdote : anecdote
          )
        })
      }
    })
    

    Interpretation:

    queryClient.invalidateQueries: This function renders the queries that use the specified query key invalid. After that, whenever the query key is used or the component mounts again, react-query will automatically retrieve the data again. When you want to make sure that your data is constantly current without having to manually manage cache changes, this is helpful.

    queryClient.setQueryData: This technique is used to immediately refresh the cache. You may make sure that the cache is up to date with your data by including a function to update the full list. This can cause your component to reload.

    Additional Tips:

    Use Optimistic Updates: If you want to provide a smoother user experience by immediately updating the UI before the server responds, you can use optimistic updates. This involves updating the local state or cache optimistically, assuming the server request will succeed. Here’s an example:

    const updateAnecdoteMutation = useMutation({
      mutationFn: updateAnecdote,
      onMutate: async (updatedAnecdote) => {
        await queryClient.cancelQueries(['anecdotes'])
        const previousAnecdotes = queryClient.getQueryData(['anecdotes'])
        
        queryClient.setQueryData(['anecdotes'], (oldData) =>
          oldData.map(anecdote =>
            anecdote.id === updatedAnecdote.id ? updatedAnecdote : anecdote
          )
        )
        return { previousAnecdotes }
      },
      onError: (err, updatedAnecdote, context) => {
        queryClient.setQueryData(['anecdotes'], context.previousAnecdotes)
      },
      onSettled: () => {
        queryClient.invalidateQueries(['anecdotes'])
      }
    })
    

    In this configuration:

    • Optimistic updates can be carried out by calling onMutate, which
      comes before the mutation function.
    • In the event that the mutation fails, onError lets you go back to the
      initial state.
    • Regardless of the outcome of the mutation, onSettled guarantees that
      the query is invalidated.

    The re-rendering problem following the update operation should be resolved if you take care of the cache update logic in your onSuccess function.

    Login or Signup to reply.
  2. in your current code you are getting the old data list and adding the new anecdote to the list, so that does not trigger a new get call to fetch the latest state of server.

    Solution:

    just simply invalidate the anecdote key like so:

      const mutation = useMutation(createAnecdote, {
        onSuccess: () => {
          queryClient.invalidateQueries('anecdotes'); // invalidate and call getAnecdotes
        },
      });
    

    you can apply the same for updating an item.

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