Skip to content

Commit

Permalink
feat(react-query): react query todo list 구현
Browse files Browse the repository at this point in the history
리액트 쿼리를 이용해서 todolist 구현해봄
api router를 backend api로 사용

close #18
  • Loading branch information
ppark2ya committed Dec 4, 2021
1 parent 6fd6a05 commit 45965fe
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 177 deletions.
9 changes: 9 additions & 0 deletions web/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"singleQuote": true,
"semi": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80,
"parser": "typescript"
}
4 changes: 2 additions & 2 deletions web/client/pocket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from "axios";
import axios from 'axios';

const ENDPOINT = "https://pokeapi.co/api/v2/pokemon";
const ENDPOINT = 'https://pokeapi.co/api/v2/pokemon';

export interface IPokemon {
count: number;
Expand Down
60 changes: 60 additions & 0 deletions web/components/Todo/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useQueryClient } from 'react-query';
import { Todo } from '~/data/todo/todo.model';
import { useUpdateTodo, useDeleteTodo } from '~/data/todo/todo.hooks';
import { ChangeEvent, useState } from 'react';

interface TodoItemProps {
todo: Todo;
}

function TodoItem({ todo }: TodoItemProps) {
const queryClient = useQueryClient();
const [value, setValue] = useState(todo.text);

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};

const updateMutation = useUpdateTodo({
onError(error) {
console.error(error);
},
});

const deleteMutation = useDeleteTodo({
onSuccess() {
queryClient.invalidateQueries('todos');
},
onError(error) {
console.error(error);
},
});

const onUpdate = (todo: Todo) => {
updateMutation.mutate({
...todo,
});
};

const onDelete = (id: number) => {
deleteMutation.mutate({
id,
});
};

return (
<div className="Todo__Item" key={todo.id}>
<p>
<input type="text" value={value} onChange={onChange} />
<button className="Update__Button" onClick={() => onUpdate(todo)}>
Update
</button>
<button className="Delete__Button" onClick={() => onDelete(todo.id)}>
X
</button>
</p>
</div>
);
}

export default TodoItem;
23 changes: 23 additions & 0 deletions web/data/todo/todo.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import axios from 'axios';
import { Todo } from './todo.model';

const ENDPOINT = 'http://localhost:3000/api';

export async function getTodos() {
const { data } = await axios.get<Todo[]>(`${ENDPOINT}/todo`);
return data;
}

export async function createTodo(requestBody: Todo) {
return axios.post<unknown>(`${ENDPOINT}/todo`, requestBody);
}

export async function updateTodo(requestBody: Todo) {
return await axios.put<unknown>(`${ENDPOINT}/todo`, requestBody);
}

export async function deleteTodo(data: { id: number }) {
return await axios.delete<unknown>(`${ENDPOINT}/todo`, {
data,
});
}
87 changes: 87 additions & 0 deletions web/data/todo/todo.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
useQuery,
UseQueryOptions,
useMutation,
UseMutationOptions,
} from 'react-query';
import { AxiosResponse, AxiosError } from 'axios';

import { Todo } from './todo.model';
import { getTodos, createTodo, updateTodo, deleteTodo } from './todo.api';

export function useTodoList(
/**
* @see https://github.com/tannerlinsley/react-query/discussions/1195
* query variables가 필요할 때
*/
/**
* @see https://github.com/tannerlinsley/react-query/discussions/1477
* TQueryFnData : Query 함수의 반환 데이터
* TError: Query 함수의 에러 반환값
* TData: Query 함수의 최종 데이터
*/
options:
| UseQueryOptions<Todo[], AxiosError<unknown>, Todo[]>
| undefined = {},
) {
return useQuery<Todo[], AxiosError>('todos', getTodos, {
retry: 2,
...options,
});
}

export function useCreateTodo(
/**
* TData: 결과값
* TError
* TVariables: mutation variables
*/
options: UseMutationOptions<
AxiosResponse<unknown>,
AxiosError<unknown>,
Todo
>,
) {
return useMutation<AxiosResponse<unknown>, AxiosError, Todo>(
'createTodo',
createTodo,
{
retry: false,
...options,
},
);
}

export function useUpdateTodo(
options: UseMutationOptions<
AxiosResponse<unknown>,
AxiosError<unknown>,
Todo
>,
) {
return useMutation<AxiosResponse<unknown>, AxiosError, Todo>(
'updateTodo',
updateTodo,
{
retry: false,
...options,
},
);
}

export function useDeleteTodo(
options: UseMutationOptions<
AxiosResponse<unknown>,
AxiosError<unknown>,
{ id: number }
>,
) {
return useMutation<AxiosResponse<unknown>, AxiosError, { id: number }>(
'deleteTodo',
deleteTodo,
{
retry: false,
...options,
},
);
}
5 changes: 5 additions & 0 deletions web/data/todo/todo.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type Todo = {
id: number;
text: string;
isDone: boolean;
};
8 changes: 4 additions & 4 deletions web/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { Hydrate, QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';

const queryClient = new QueryClient();
queryClient.setDefaultOptions({
Expand Down
47 changes: 47 additions & 0 deletions web/pages/api/todo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { NextApiRequest, NextApiResponse } from 'next';

type Todo = {
id: number;
text: string;
isDone: boolean;
};

let initialState: Todo[] = [
{ id: 0, text: 'React-Query 스터디', isDone: false },
{ id: 1, text: 'jest 스터디', isDone: false },
{ id: 2, text: 'msw 스터디', isDone: false },
];

export default (req: NextApiRequest, res: NextApiResponse) => {
const { method, body } = req;

switch (req.method) {
case 'GET':
res.status(200).json(initialState);
break;
case 'POST':
initialState.push(body);
res.status(200).json({ message: 'create todo success' });
break;
case 'PUT':
initialState = initialState.map((todo) => {
if (todo.id === body.id) {
return {
...todo,
...body,
};
} else {
return todo;
}
});
res.status(200).json({ message: 'update todo success' });
break;
case 'DELETE':
initialState = initialState.filter((todo) => todo.id !== body.id);
res.status(200).json({ message: 'delete todo success' });
break;
default:
res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
Loading

0 comments on commit 45965fe

Please sign in to comment.