Reputation: 419
I am learning TanStack Query (previously React Query) and so far I menaged to successfully fetch data. I am wondering how to mutate it so that I can take the advantage of all TanStack Query features - especially caching. Here is my code so far:
import { useQuery } from "@tanstack/react-query"
type Task {
id: number,
name: string
}
function convertDataToTasks(data:any[]){
const tasks:Task[] = [];
data.forEach( item => {
tasks.push({ id: item.id, name: item.title})
});
return tasks;
}
export function toDosQuery() {
return useQuery({
queryKey: ['todos'],
queryFn: async (): Promise<Array<any>> => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos')
return await response.json()
},
})
}
export function TaskLabel({task}:{task:Task}) {
return (
<article>
{ task.name }
</article>
)
}
export function SidebarTasksList() {
const { status, data, error, isFetching } = toDosQuery()
return (
<section>
{status === 'pending' ? (
'Loading...'
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
convertDataToTasks(data).map( task => (
<TaskLabel task={task} key={task.id}/>
))
)}
<span>{isFetching ? "Fetching new data" : ""}</span>
</section>
)
}
function App() {
return (
<>
app root cmp
<SidebarTasksList />
</>
)
}
export default App
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import App from './App.tsx'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={new QueryClient}>
<ReactQueryDevtools />
<App />
</QueryClientProvider>
</StrictMode>
)
Right now I parse received data by passing it into dedicated function convertDataToTasks()
in JSX returned from component which feels unelegant. I would like to keep already parsed data in the Query cache as I plan on consuming external public API which provide much more details and different formating than what I will need for this app.
Upvotes: 1
Views: 750
Reputation: 868
Start by setting up a custom hook with useMutation
:
const useMutateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: editTodo,
// ...options
});
};
Now to actually do something when you update the data, we must add the onSuccess
option. We can use both invalidateQueries and setQueryData for handling cache updates.
The onSuccess callback in the example below identifies the todo item with the same ID as the updated data and modifies it directly without refetching the entire todo list.
const useMutateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: editTodo,
onSuccess: (data, variables) => {
queryClient.setQueryData(["todo", { id: variables.id }], data);
},
});
};
It's up to you whether you want to update individual items or refetch the entire list. Here's a comment from TkDodo that might help you decide:
an invalidation will always refetch the entire list. That's often a good thing - items could have been added or altered by someone else in the meantime, so now you'd have the up-to-date data.
const useMutateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: editTodo(),
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
};
Now it’s up to you how you want to use the functions described above. Here’s a quick example to show how you can put it all together:
import { useMutation, useQueryClient } from "@tanstack/react-query";
type Task = {
id: number;
name: string;
};
const editTodo = async (newTask: Task) => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos", {
method: "POST",
body: JSON.stringify(newTask),
});
return await response.json();
};
const useMutateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: editTodo,
onSuccess: (data, variables) => {
queryClient.setQueryData(["todos", { id: variables.id }], data);
},
});
};
function TodoItem({ task }: { task: Task }) {
const mutateTodo = useMutateTodo();
return (
<button onClick={() => mutateTodo.mutate({ id: 1, name: "New Task" })}>
edit todo
</button>
);
}
Upvotes: 0
Reputation: 202846
If I'm understanding your post/question correctly you are saying you would prefer to not call convertDataToTasks
inline in JSX when rendering and would like React-Query to cache the data you want to render from. If this is the case then I believe the solution is as simple as moving the convertDataToTasks
call into the queryFn
query handler you are using.
Example:
interface JsonPlaceholderTask {
userId: number;
id: number;
title: string;
completed: boolean;
}
type Task = {
id: number;
name: string;
};
const convertDataToTasks = (data: JsonPlaceholderTask[]) =>
data.map((item) => ({ id: item.id, name: item.title })) as Task[];
function toDosQuery() {
return useQuery({
queryKey: ["todos"],
queryFn: async (): Promise<Task[]> => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos"
);
const data: JsonPlaceholderTask[] = await response.json();
return convertDataToTasks(data);
},
});
}
function SidebarTasksList() {
const { status, data, error, isFetching } = toDosQuery();
return (
<section>
{status === "pending" ? (
"Loading..."
) : status === "error" ? (
<span>Error: {error.message}</span>
) : (
data.map((task) => <TaskLabel task={task} key={task.id} />)
)}
<span>{isFetching ? "Fetching new data" : ""}</span>
</section>
);
}
Previously cached data: the returned JSON placeholder data
Currently cached data: the mapped Task
data
Upvotes: 1