Feat(frontend): Create/Edit/Delete todos
This commit is contained in:
		@@ -79,5 +79,5 @@
 | 
				
			|||||||
  "prettier": {
 | 
					  "prettier": {
 | 
				
			||||||
    "trailingComma": "all"
 | 
					    "trailingComma": "all"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "proxy": "http://localhost:5050"
 | 
					  "proxy": "http://127.0.0.1:5050"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import ScrollToTop from "./components/ScrollToTop";
 | 
					import ScrollToTop from "./components/ScrollToTop";
 | 
				
			||||||
import TopBar from "./components/TopBar";
 | 
					import TopBar from "./components/TopBar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Register from "./pages/Register";
 | 
					import Register from "./pages/Register";
 | 
				
			||||||
import ConfirmEmail from "./pages/ConfirmEmail";
 | 
					import ConfirmEmail from "./pages/ConfirmEmail";
 | 
				
			||||||
import Login from "./pages/Login";
 | 
					import Login from "./pages/Login";
 | 
				
			||||||
@@ -10,6 +11,10 @@ import ChangePassword from "./pages/ChangePassword";
 | 
				
			|||||||
import ForgottenPassword from "./pages/ForgottenPassword";
 | 
					import ForgottenPassword from "./pages/ForgottenPassword";
 | 
				
			||||||
import ResetPassword from "./pages/ResetPassword";
 | 
					import ResetPassword from "./pages/ResetPassword";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CreateTodo from "./pages/CreateTodo";
 | 
				
			||||||
 | 
					import EditTodo from "./pages/EditTodo";
 | 
				
			||||||
 | 
					import Todos from "./pages/Todos";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Router = () => (
 | 
					const Router = () => (
 | 
				
			||||||
  <BrowserRouter>
 | 
					  <BrowserRouter>
 | 
				
			||||||
    <ScrollToTop />
 | 
					    <ScrollToTop />
 | 
				
			||||||
@@ -28,6 +33,30 @@ const Router = () => (
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
      <Route path="/forgotten-password/" element={<ForgottenPassword />} />
 | 
					      <Route path="/forgotten-password/" element={<ForgottenPassword />} />
 | 
				
			||||||
      <Route path="/reset-password/:token/" element={<ResetPassword />} />
 | 
					      <Route path="/reset-password/:token/" element={<ResetPassword />} />
 | 
				
			||||||
 | 
					      <Route
 | 
				
			||||||
 | 
					        path="/"
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <RequireAuth>
 | 
				
			||||||
 | 
					            <Todos />
 | 
				
			||||||
 | 
					          </RequireAuth>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <Route
 | 
				
			||||||
 | 
					        path="/todos/new/"
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <RequireAuth>
 | 
				
			||||||
 | 
					            <CreateTodo />
 | 
				
			||||||
 | 
					          </RequireAuth>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <Route
 | 
				
			||||||
 | 
					        path="/todos/:id/"
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <RequireAuth>
 | 
				
			||||||
 | 
					            <EditTodo />
 | 
				
			||||||
 | 
					          </RequireAuth>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
    </Routes>
 | 
					    </Routes>
 | 
				
			||||||
  </BrowserRouter>
 | 
					  </BrowserRouter>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								frontend/src/components/Todo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								frontend/src/components/Todo.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					import Checkbox from "@mui/material/Checkbox";
 | 
				
			||||||
 | 
					import IconButton from "@mui/material/IconButton";
 | 
				
			||||||
 | 
					import ListItem from "@mui/material/ListItem";
 | 
				
			||||||
 | 
					import ListItemButton from "@mui/material/ListItemButton";
 | 
				
			||||||
 | 
					import ListItemIcon from "@mui/material/ListItemIcon";
 | 
				
			||||||
 | 
					import ListItemText from "@mui/material/ListItemText";
 | 
				
			||||||
 | 
					import Skeleton from "@mui/material/Skeleton";
 | 
				
			||||||
 | 
					import DeleteIcon from "@mui/icons-material/Delete";
 | 
				
			||||||
 | 
					import { format } from "date-fns";
 | 
				
			||||||
 | 
					import { Link } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Todo as TodoModel } from "../models";
 | 
				
			||||||
 | 
					import { useDeleteTodoMutation } from "../queries";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IProps {
 | 
				
			||||||
 | 
					  todo?: TodoModel;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Todo = ({ todo }: IProps) => {
 | 
				
			||||||
 | 
					  const { mutateAsync: deleteTodo } = useDeleteTodoMutation();
 | 
				
			||||||
 | 
					  let secondary;
 | 
				
			||||||
 | 
					  if (todo === undefined) {
 | 
				
			||||||
 | 
					    secondary = <Skeleton width="200px" />;
 | 
				
			||||||
 | 
					  } else if (todo.due !== null) {
 | 
				
			||||||
 | 
					    secondary = format(todo.due, "P");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ListItem
 | 
				
			||||||
 | 
					      secondaryAction={
 | 
				
			||||||
 | 
					        <IconButton
 | 
				
			||||||
 | 
					          disabled={todo === undefined}
 | 
				
			||||||
 | 
					          edge="end"
 | 
				
			||||||
 | 
					          onClick={() => deleteTodo(todo?.id!)}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <DeleteIcon />
 | 
				
			||||||
 | 
					        </IconButton>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <ListItemButton
 | 
				
			||||||
 | 
					        component={Link}
 | 
				
			||||||
 | 
					        disabled={todo === undefined}
 | 
				
			||||||
 | 
					        to={`/todos/${todo?.id}/`}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <ListItemIcon>
 | 
				
			||||||
 | 
					          <Checkbox
 | 
				
			||||||
 | 
					            checked={todo?.complete ?? false}
 | 
				
			||||||
 | 
					            disabled
 | 
				
			||||||
 | 
					            disableRipple
 | 
				
			||||||
 | 
					            edge="start"
 | 
				
			||||||
 | 
					            tabIndex={-1}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </ListItemIcon>
 | 
				
			||||||
 | 
					        <ListItemText
 | 
				
			||||||
 | 
					          primary={todo?.task ?? <Skeleton />}
 | 
				
			||||||
 | 
					          secondary={secondary}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </ListItemButton>
 | 
				
			||||||
 | 
					    </ListItem>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export default Todo;
 | 
				
			||||||
							
								
								
									
										44
									
								
								frontend/src/components/TodoForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/src/components/TodoForm.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { Form, Formik } from "formik";
 | 
				
			||||||
 | 
					import * as yup from "yup";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import CheckboxField from "../components/CheckboxField";
 | 
				
			||||||
 | 
					import DateField from "../components/DateField";
 | 
				
			||||||
 | 
					import FormActions from "../components/FormActions";
 | 
				
			||||||
 | 
					import TextField from "../components/TextField";
 | 
				
			||||||
 | 
					import type { ITodoData } from "../queries";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IProps {
 | 
				
			||||||
 | 
					  initialValues: ITodoData;
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
 | 
					  onSubmit: (data: ITodoData) => Promise<any>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validationSchema = yup.object({
 | 
				
			||||||
 | 
					  complete: yup.boolean(),
 | 
				
			||||||
 | 
					  due: yup.date().nullable(),
 | 
				
			||||||
 | 
					  task: yup.string().required("Required"),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TodoForm = ({ initialValues, label, onSubmit }: IProps) => (
 | 
				
			||||||
 | 
					  <Formik<ITodoData>
 | 
				
			||||||
 | 
					    initialValues={initialValues}
 | 
				
			||||||
 | 
					    onSubmit={onSubmit}
 | 
				
			||||||
 | 
					    validationSchema={validationSchema}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    {({ dirty, isSubmitting }) => (
 | 
				
			||||||
 | 
					      <Form>
 | 
				
			||||||
 | 
					        <TextField fullWidth label="Task" name="task" required />
 | 
				
			||||||
 | 
					        <DateField fullWidth label="Due" name="due" />
 | 
				
			||||||
 | 
					        <CheckboxField fullWidth label="Complete" name="complete" />
 | 
				
			||||||
 | 
					        <FormActions
 | 
				
			||||||
 | 
					          disabled={!dirty}
 | 
				
			||||||
 | 
					          isSubmitting={isSubmitting}
 | 
				
			||||||
 | 
					          label={label}
 | 
				
			||||||
 | 
					          links={[{ label: "Back", to: "/" }]}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Form>
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					  </Formik>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TodoForm;
 | 
				
			||||||
							
								
								
									
										36
									
								
								frontend/src/pages/CreateTodo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								frontend/src/pages/CreateTodo.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import TodoForm from "../components/TodoForm";
 | 
				
			||||||
 | 
					import Title from "../components/Title";
 | 
				
			||||||
 | 
					import type { ITodoData } from "../queries";
 | 
				
			||||||
 | 
					import { useCreateTodoMutation } from "../queries";
 | 
				
			||||||
 | 
					import { ToastContext } from "../ToastContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CreateTodo = () => {
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					  const { mutateAsync: createTodo } = useCreateTodoMutation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = async (data: ITodoData) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await createTodo(data);
 | 
				
			||||||
 | 
					      navigate("/");
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      addToast("Try Again", "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Create a Todo" />
 | 
				
			||||||
 | 
					      <TodoForm
 | 
				
			||||||
 | 
					        initialValues={{ complete: false, due: null, task: "" }}
 | 
				
			||||||
 | 
					        label="Create"
 | 
				
			||||||
 | 
					        onSubmit={onSubmit}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CreateTodo;
 | 
				
			||||||
							
								
								
									
										52
									
								
								frontend/src/pages/EditTodo.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/src/pages/EditTodo.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					import Skeleton from "@mui/material/Skeleton";
 | 
				
			||||||
 | 
					import { useContext } from "react";
 | 
				
			||||||
 | 
					import { useNavigate, useParams } from "react-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import TodoForm from "../components/TodoForm";
 | 
				
			||||||
 | 
					import Title from "../components/Title";
 | 
				
			||||||
 | 
					import type { ITodoData } from "../queries";
 | 
				
			||||||
 | 
					import { useEditTodoMutation, useTodoQuery } from "../queries";
 | 
				
			||||||
 | 
					import { ToastContext } from "../ToastContext";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Iparams {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EditTodo = () => {
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const params = useParams<keyof Iparams>() as Iparams;
 | 
				
			||||||
 | 
					  const todoId = parseInt(params.id, 10);
 | 
				
			||||||
 | 
					  const { addToast } = useContext(ToastContext);
 | 
				
			||||||
 | 
					  const { data: todo } = useTodoQuery(todoId);
 | 
				
			||||||
 | 
					  const { mutateAsync: editTodo } = useEditTodoMutation(todoId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSubmit = async (data: ITodoData) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await editTodo(data);
 | 
				
			||||||
 | 
					      navigate("/");
 | 
				
			||||||
 | 
					    } catch {
 | 
				
			||||||
 | 
					      addToast("Try again", "error");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Title title="Edit todo" />
 | 
				
			||||||
 | 
					      {todo === undefined ? (
 | 
				
			||||||
 | 
					        <Skeleton height="80px" />
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        <TodoForm
 | 
				
			||||||
 | 
					          initialValues={{
 | 
				
			||||||
 | 
					            complete: todo.complete,
 | 
				
			||||||
 | 
					            due: todo.due,
 | 
				
			||||||
 | 
					            task: todo.task,
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          label="Edit"
 | 
				
			||||||
 | 
					          onSubmit={onSubmit}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EditTodo;
 | 
				
			||||||
							
								
								
									
										38
									
								
								frontend/src/pages/Todos.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/src/pages/Todos.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import Fab from "@mui/material/Fab";
 | 
				
			||||||
 | 
					import List from "@mui/material/List";
 | 
				
			||||||
 | 
					import AddIcon from "@mui/icons-material/Add";
 | 
				
			||||||
 | 
					import { Link, Navigate } from "react-router-dom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Todo from "../components/Todo";
 | 
				
			||||||
 | 
					import { useTodosQuery } from "../queries";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Todos = () => {
 | 
				
			||||||
 | 
					  const { data: todos } = useTodosQuery();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (todos?.length === 0) {
 | 
				
			||||||
 | 
					    return <Navigate to="/todos/new/" />;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        <List>
 | 
				
			||||||
 | 
					          {todos !== undefined
 | 
				
			||||||
 | 
					            ? todos.map((todo) => <Todo key={todo.id} todo={todo} />)
 | 
				
			||||||
 | 
					            : [1, 2, 3].map((id) => <Todo key={-id} />)}
 | 
				
			||||||
 | 
					        </List>
 | 
				
			||||||
 | 
					        <Fab
 | 
				
			||||||
 | 
					          component={Link}
 | 
				
			||||||
 | 
					          sx={{
 | 
				
			||||||
 | 
					            bottom: (theme) => theme.spacing(2),
 | 
				
			||||||
 | 
					            position: "fixed",
 | 
				
			||||||
 | 
					            right: (theme) => theme.spacing(2),
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          to="/todos/new/"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <AddIcon />
 | 
				
			||||||
 | 
					        </Fab>
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Todos;
 | 
				
			||||||
							
								
								
									
										72
									
								
								frontend/src/queries.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								frontend/src/queries.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					import { useQueryClient } from "@tanstack/react-query";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Todo } from "./models";
 | 
				
			||||||
 | 
					import { useMutation, useQuery } from "./query";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const STALE_TIME = 1000 * 60 * 5; // 5 mins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useTodosQuery = () =>
 | 
				
			||||||
 | 
					  useQuery<Todo[]>(
 | 
				
			||||||
 | 
					    ["todos"],
 | 
				
			||||||
 | 
					    async () => {
 | 
				
			||||||
 | 
					      const response = await axios.get("/todos/");
 | 
				
			||||||
 | 
					      return response.data.todos.map((json: any) => new Todo(json));
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    { staleTime: STALE_TIME },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useTodoQuery = (id: number) => {
 | 
				
			||||||
 | 
					  const queryClient = useQueryClient();
 | 
				
			||||||
 | 
					  return useQuery<Todo>(
 | 
				
			||||||
 | 
					    ["todos", id.toString()],
 | 
				
			||||||
 | 
					    async () => {
 | 
				
			||||||
 | 
					      const response = await axios.get(`/todos/${id}/`);
 | 
				
			||||||
 | 
					      return new Todo(response.data);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      initialData: () => {
 | 
				
			||||||
 | 
					        return queryClient
 | 
				
			||||||
 | 
					          .getQueryData<Todo[]>(["todos"])
 | 
				
			||||||
 | 
					          ?.filter((todo: Todo) => todo.id === id)[0];
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      staleTime: STALE_TIME,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ITodoData {
 | 
				
			||||||
 | 
					  complete: boolean;
 | 
				
			||||||
 | 
					  due: Date | null;
 | 
				
			||||||
 | 
					  task: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useCreateTodoMutation = () => {
 | 
				
			||||||
 | 
					  const queryClient = useQueryClient();
 | 
				
			||||||
 | 
					  return useMutation(
 | 
				
			||||||
 | 
					    async (data: ITodoData) => await axios.post("/todos/", data),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      onSuccess: () => queryClient.invalidateQueries(["todos"]),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useEditTodoMutation = (id: number) => {
 | 
				
			||||||
 | 
					  const queryClient = useQueryClient();
 | 
				
			||||||
 | 
					  return useMutation(
 | 
				
			||||||
 | 
					    async (data: ITodoData) => await axios.put(`/todos/${id}/`, data),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      onSuccess: () => queryClient.invalidateQueries(["todos"]),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useDeleteTodoMutation = () => {
 | 
				
			||||||
 | 
					  const queryClient = useQueryClient();
 | 
				
			||||||
 | 
					  return useMutation(
 | 
				
			||||||
 | 
					    async (id: number) => await axios.delete(`/todos/${id}/`),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      onSuccess: () => queryClient.invalidateQueries(["todos"]),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Reference in New Issue
	
	Block a user