Пройдя курс Fullstackopen, я прохожу часть 6 и изучаю useQUery/useMutation.
Я застрял ближе к концу, потому что мое приложение не будет повторно отображаться после того, как я вызову API и внесу изменения в список элементов. Я попробовал fetchPolicy:'false'
в параметрах запроса.
Другая логика работает: мне нужно добавить элемент и обновить его, но на вкладке сети инструментов разработки я вижу, что при запуске обновления я получил запрос «PUT», но за ним не последовал запрос «GET», так что я думаю рендеринг, который должен быть запущен с помощью «onSuccess», не запускается.
Я не понимаю, почему, и я покопался в Интернете и здесь, чтобы найти какой-то вариант использования, но не смог понять.
Спасибо всем!
Вот 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 (...)
АнекдотФорм.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.info('new anecdote')
newAnecdoteMutation.mutate({ content, votes: 0 })
}
return (...)
запрос.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)
}
Анализ проблемы:
1. Обновление кэша:
React-query — это то, что вы используете для кэширования и выборки данных. Вы используете queryClient.setQueryData для обновления кэшированных данных для этого конкретного анекдота после его изменения с помощью updateAnecdoteMutation. Возможно, ваш список не будет перерисовываться повторно, поскольку вы обновляете кеш только для конкретного анекдота, а не для всей коллекции анекдотов.
2. Отсутствие автоматического обновления:
Вы сообщили, что на вкладке сети отображается запрос «PUT», но после него не отображается запрос «GET». Это говорит о том, что после мутации не происходит автоматического обновления, скорее всего, потому, что queryClient.setQueryData не инициирует повторную выборку; вместо этого он просто обновляет кэш локально.
Резолюции:
Первое решение: обновите запрос и сделайте его недействительным.
Чтобы повторно получить весь список анекдотов, используйте queryClient.invalidateQueries. Это гарантирует, что после обновления компонент повторно отрисовывается с обновленными данными и что с сервера будут получены самые последние данные.
Чтобы обновить анекдот, обновите метод onSuccess в хуке useMutation:
const updateAnecdoteMutation = useMutation({
mutationFn: updateAnecdote,
onSuccess: () => {
queryClient.invalidateQueries(['anecdotes'])
}
})
Сделав это, вы можете убедиться, что данные актуальны, сделав запрос анекдотов недействительным и заставив его перезагрузить.
Второе решение: обновить кэш списка вручную.
Убедитесь, что вы обновляете весь список в кеше, а не только конкретный анекдот, если вы хотите вручную обновить кеш списка, а не выполнять повторную выборку. Хотя этот метод предотвращает дальнейшие сетевые запросы, он требует обновления кэша вручную.
Чтобы изменить полный список анекдотов, обновите метод onSuccess:
const updateAnecdoteMutation = useMutation({
mutationFn: updateAnecdote,
onSuccess: (updatedAnecdote) => {
queryClient.setQueryData(['anecdotes'], (oldData) => {
return oldData.map(anecdote =>
anecdote.id === updatedAnecdote.id ? updatedAnecdote : anecdote
)
})
}
})
Интерпретация:
queryClient.invalidateQueries: эта функция делает запросы, использующие указанный ключ запроса, недействительными. После этого всякий раз, когда используется ключ запроса или компонент снова монтируется, ответный запрос снова автоматически извлекает данные. Это полезно, если вы хотите быть уверенным, что ваши данные постоянно актуальны, без необходимости вручную управлять изменениями кэша.
queryClient.setQueryData: этот метод используется для немедленного обновления кеша. Вы можете убедиться, что кеш обновлен с вашими данными, включив функцию обновления полного списка. Это может привести к перезагрузке вашего компонента.
Дополнительные советы:
Используйте оптимистические обновления. Если вы хотите обеспечить более плавное взаимодействие с пользователем, немедленно обновляя пользовательский интерфейс до того, как сервер ответит, вы можете использовать оптимистические обновления. Это включает в себя оптимистическое обновление локального состояния или кэша при условии, что запрос сервера будет успешным. Вот пример:
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'])
}
})
В этой конфигурации:
Проблема повторного рендеринга после операции обновления должна быть решена, если вы позаботитесь о логике обновления кэша в вашей функции onSuccess.
в вашем текущем коде вы получаете старый список данных и добавляете в список новый анекдот, чтобы не запускать новый вызов get
для получения последнего состояния сервера.
Решение:
просто сделайте недействительным ключ anecdote
следующим образом:
const mutation = useMutation(createAnecdote, {
onSuccess: () => {
queryClient.invalidateQueries('anecdotes'); // invalidate and call getAnecdotes
},
});
вы можете применить то же самое для обновления элемента.
Вызовы API после голосования за анекдот:
Сделал это, но с массивом ['anecdotes']
или аргументом {queryKey: ['anecdotes']}
То же поведение и без выборки. После этого я даже попытался добавить queryClient.fetchQuery([...])
, но безуспешно.
Можете ли вы разместить свой проект на GitHub или codeandbox?
Обязательно: github.com/realpoap/fullstack-open/tree/main/Part%206/…
клонировал ваш репозиторий, и он работает так, как ожидалось. после голосования он вызывает метод get
, а также обновляется пользовательский интерфейс. Я отредактировал свой ответ, чтобы показать вам фотографию вызовов API.
Это так странно! Думаю, тогда мне пора двигаться дальше. Спасибо за ваше время !
Ого, это очень подробный ответ! Большое спасибо ! К сожалению, это не работает (решения 1 и 2). Должен ли я упомянуть, что перед запросом PUT был сделан первый запрос под названием OPTION? Я обновил updateAnecdoteMutation вот так:
const updateAnecdoteMutation = useMutation({ mutationFn: updateAnecdote, onSuccess: () => { console.info('updating') queryClient.invalidateQueries(['anecdotes']) // queryClient.setQueryData(['anecdotes', updatedAnecdote.id], updatedAnecdote) console.info('after invalidateQueries'); } })