skip to Main Content

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.

2

Answers


  1. 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

      previously cached data

    • Currently cached data: the mapped Task data

      currently cached data

    Demo

    Edit how-to-parse-data-received-from-tanstack-query-react-query-to-keep-it-in-cache

    Login or Signup to reply.
  2. 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.

    Updating a single todo item after mutation:

    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.

    Invalidate and refetch the entire list after mutation:

    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>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search