loading...

Build a MERN stack todo app using react-query

ashiqcseworld profile image Ashiqur Rahman ・3 min read

React-query is a Hooks for fetching, caching and updating asynchronous data in React. Many of us use redux or context API to manage our server state (asynchronous state). But What if we stop trying to manage our server state in our frontend code and instead treat it like a cache that just needs to be updated periodically? I think this will make our code more accessible to pure frontend developers.

Fetch Todos

const fetchPosts = async () => {
  const {
    data: { todos },
  } = await axios.get(`http://localhost:7777/api`);
  return todos;
};

const { data: todos, isLoading, error } = useQuery("tasks", fetchPosts);
Enter fullscreen mode Exit fullscreen mode

You can see from the above snippet, I give 2 argument on useQuery where tasks is the key (we can refetch the data by using that key) and fetchPosts is the callback function in which I call my express-API.

useQuery gives data, isLoading, error state. It reduces lots of boilerplate code.

Create a todo.

We will create todoItem using useMutation

Todo form

<form
  className="form"
  onSubmit={(e) => {
    e.preventDefault();
    mutate({ task: value });
    reset();
  }}
>
  <input
    type="text"
    className="form__box"
    onChange={onChangeHandler}
    name="task"
    value={value}
    placeholder="Enter a task..."
    required
  />

  <button className="form__button">Add Task</button>
</form>;


Enter fullscreen mode Exit fullscreen mode

useMutation

const [mutate] = useMutation(
  (values) => axios.post("http://localhost:7777/api", values),
  {
    onSettled: () => {
      queryCache.refetchQueries("tasks");
    },
  }
);

Enter fullscreen mode Exit fullscreen mode

I have called the mutate function when the form is submitted. and gave newTask as an argument. useMutation got that value as a parameter and send a post request to my express API. as a result a todo is created.

But, we can take this to next level. what if I want to see the submitted todo task before the api call is finished? Here comes an optimistic update. We will optimistically update the UI for a better User experience.

Optimistic Update

const [mutate] = useMutation(
  (values) => axios.post("http://localhost:7777/api", values),
  {
    onMutate: (newTask) => {
      // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
      queryCache.cancelQueries("tasks");
      //   optimistic update
      const previousTasks = queryCache.getQueryData("tasks");
      queryCache.setQueryData("tasks", (old) => [...old, newTask]);
      return () => queryCache.setQueryData("tasks", previousTasks);
    },

    onError: (error, newTask, rollback) => {
      //   If there is an errror, then we will reset the tasks to previous tasks
      rollback();
    },
    onSettled: () => {
      queryCache.refetchQueries("tasks");
    },
  }
);

Enter fullscreen mode Exit fullscreen mode

Look we have two new methods. one is onMutate another is onError. onMutate will call as soon as the form is submitted. so on this method, I am updating the todo list and show the updated list to the user.

//   optimistic update
const previousTasks = queryCache.getQueryData("tasks");
queryCache.setQueryData("tasks", (old) => [...old, newTask]);
Enter fullscreen mode Exit fullscreen mode

But, what if an error occurred? for example, user goes offline as soon as he submitted the new todo? here comes the onError method. It will call the rollback function (the function which is returned from onMutate) and on cache, we will set the previous list as listItems array.

And after that, we will refetch the tasks list on onSettled method.

I think, If you understand create todo, you will easily understand delete and update items.

Update Item Form

<form
  onSubmit={(e) => {
    e.preventDefault();
    mutate({ task: value });
    reset();
    toggle(false);
  }}
>
  <input
    type="text"
    autoFocus
    className="edit"
    onChange={handleChange}
    value={value}
  />
</form>;
Enter fullscreen mode Exit fullscreen mode

Update Item Mutation

const [mutate] = useMutation(
  (newTodo) => axios.put(`http://localhost:7777/api/${todo._id}`, newTodo),
  {
    onMutate: (newTask) => {
      console.log(newTask);
      // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
      queryCache.cancelQueries("tasks");

      // Snapshot the previous value
      const previousTask = queryCache.getQueryData("tasks");

      // Optimistically update to the new value
      queryCache.setQueryData("tasks", (oldTasks) =>
        oldTasks.map((item) =>
          item._id === todo._id ? { ...item, task: newTask.task } : item
        )
      );

      return () => queryCache.setQueryData("tasks", previousTask);
    },

    onError: (error, newTask, rollback) => {
      //   If there is an errror, then we will reset the tasks to previous tasks
      rollback();
    },
    onSettled: (data, error, newTask) => {
      queryCache.refetchQueries("tasks");
    },
  }
);
Enter fullscreen mode Exit fullscreen mode

Delete Item

 const [mutateToggle] = useMutation(
    (values) => axios.patch(`http://localhost:7777/api/${todo._id}`),
    {
      onSettled: () => {
        queryCache.refetchQueries('tasks');
      },
    }
  );
Enter fullscreen mode Exit fullscreen mode

You will get the full working code on this repository.
React-query Todo App

Discussion

pic
Editor guide