원티드 프리온보딩 챌린지 리팩토링 2회차

Jin·2022년 8월 19일
0
post-thumbnail

원티드에서 진행을 하는 프리온보딩 프론트엔드 챌린지 8월 강의를 들으며 배운것과 코드를 구현한것에 관해 정리하고자 합니다.

1. React-Query 적용 해보기

예전부터 계속 미뤄왔던 react-query 공부를 드디어 시작하며, 기존의 todo-app에 적용을 해보았다.

먼저 리액트 쿼리를 설치해주었다.
설치 방법 : yarn add @tanstack/react-query

설치 이후, 모든 컴포넌트에서 사용하도록 아래와 같이 Router를 감싸주었다.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Router from "./router/router";

const queryClinet = new QueryClient();
function App() {
	return (
		<QueryClientProvider client={queryClinet}>
			<Router />
		</QueryClientProvider>
	);
}

export default App;

다음으로 어떤것들을 react-query로 처리를 할까 정해야 했는데, 서버 상태를 가져와 사용하는 아래의 4가지를 모두 react-query로 처리하기로 했다.

  • todo 모든 목록 불러오기
  • todo 추가
  • todo 제거
  • todo 수정하기

현재 todo 목록을 보여주는 TodoList 컴포넌트와 todo 하나의 상세정보를 보여주는 TodoDetail 컴포넌트 2개로 구성이 되어있다. 목록을 보여주는 TodoList에서 모든 todo 불러오기, todo 추가 그리고 todo 제거 처리를 하였고 TodoDetail에서 수정 처리르 해주는 함수를 구현하였다.

1) todo 모든 목록 불러오기 리팩토링

Before

const [todos, setTodos] = useState<TodoType[]>([]);

const getTodos = async () => {
  const todo = await todoApi.getAllTodos(token!);
  setTodos(todo.data);
};

useEffect(() => {
  getTodos();
}, []);

After

const { isLoading, isError, data } = useQuery(["todos"], todoApi.getAllTodos);

리액트쿼리 적용전에는 모든 todo를 불러오는 axios요청을 하는 함수와 그 함수의 return 값을 넣어줄 useState 그리고 페이지가 렌더링되면 제일 처음 시작이 되도록 useEffect를 사용하였는데, 리액트 쿼리를 사용하니 한줄로 끝이났다..

2) todo 추가 리팩토링

Before

onSubmit: async ({ title, content }) => {
			const newTodo = {
				title,
				content,
			};
			const { data } = await todoApi.createTodo(newTodo);
			if (data) {
				navigate(`/todos/${data.id}`, { state: { item: data } });
              	getTodos()
				alert("작성 성공");
              
			}
		},
          

회원가입, 로그인 그리고 todo 생성에 모두 사용 가능한 useForm이라는 CustomHook을 사용하고 있다. 비동기적으로 axios 요청을 하여 성공을 했을시에, 해당 todo의 디테일 페이지로 이동 및 to목록을 다시 불러오기위해 getTodos()를 호출 해주었다.

After

const addTodoItem = useMutation(
		(newTodo: Pick<TodoType, "title" | "content">) =>
			todoApi.createTodo(newTodo),
		{
			onSuccess: () => {
				queryClient.invalidateQueries(["todos"]);
				values.title = "";
				values.content = "";
			},
		}
	);

onSubmit: async ({ title, content }) => {
			const newTodo = {
				title,
				content,
			};
			updateTodo.mutate(newTodo);
		},

서버 side-effect를 수행하기 위해 useMutation 을 사용하였고, 성공시에 기존에 있던 todo 목록들은 stale 하게 되므로 무효화를 시켜줌으로써 refetch를 실행하도록 하였다.

3) todo 삭제 코드 리팩토링

Before

const deleteTodo = (id: string) => {
		if (window.confirm("정말 삭제하시겠습니까?")) {
			todoApi.deleteTodo(id, token!).then(() => {
				alert("삭제가 완료되었습니다");
				getTodos();
			});
		}
	};

delete api를 요청한 이후, 요청이 성공하면 alert과 함께 getTodos() 함수를 한번더 호출하여 목록을 최신화 시켜주도록 작성을 하였다.

After

const deleteTodoItem = useMutation((id: string) => todoApi.deleteTodo(id), {
		onSuccess: () => queryClient.invalidateQueries(["todos"]),
	});

useMutation을 사용하여 todo 삭제 api 요청을 하였고,mutation 성공시에 새로운 변경사항을 나타내기 위해 invalidateQueries를 사용하여 todo 목록들을 무효화 시킴으로서 useQuery를 refetch 하였다.

4) todo 수정 코드 리팩토링

function TodoDetails() {
	const token = localStorage.getItem("token");
	const location = useLocation();
	const state = location.state as StateType;
	const { item } = state;
	const [todo, setTodo] = useState(item);
	const [isModify, setIsModify] = useState(false);

	const handleModifyMode = () => {
		setIsModify(!isModify);
	};

	const getNew = async () => {
		const newTodo = await todoApi.getTodoById(item.id, token!);
		setTodo(newTodo.data);
	};
	const { values, errors, handleChange, handleSubmit } = useForm({
		initialValues: {
			title: todo.title,
			content: todo.content,
		},
		onSubmit: async ({ title, content }) => {
			const newTodo = {
				title,
				content,
			};
			const { data } = await todoApi.updateTodo(todo.id, newTodo, token!);
			if (data) {
				getNew();
				alert("수정 성공");
				setIsModify(false);
			}
		},
		validate: ({ title, content }) => {
			const errors: { [key: string]: string } = {};
			if (title.trim().length === 0) {
				errors.title = "제목을 입력해주세요";
			}
			if (content.trim().length === 0) {
				errors.content = "내용을 입력해주세요";
			}
			return errors;
		},
	});

Link를 사용하여 페이지 이동을 하기 때문에 location과 state를 사용해 props(todo의 정보)를 전달 받았다.

수정 모드를 구분하기 위해 useState를 사용하여 버튼을 누르면 모드가 바뀌는 식으로 구현을 하였다.
todo를 추가할때에도 사용했던 useForm을 그대로 사용을 하였고 update가 되면 해당 todo detail 정보를 불러오는 getNew() 함수를 호출하였고, 수정 모드를 false 처리 해주는 방식으로 구현을 하였다.

After

function TodoDetails() {
	const { data } = useQuery(["todo", item.id], async () => {
		const res = await todoApi.getTodoById(item.id);
		return res.data;
	});

	const updateTodo = useMutation(
		(newTodo: Pick<TodoType, "title" | "content">) =>
			todoApi.updateTodo(item.id, newTodo),
		{
			onSuccess: () => {
				queryClient.invalidateQueries(["todo", item.id]);
				setIsModify(false);
			},
		}
	);

	const { values, errors, handleChange, handleSubmit } = useForm({
		initialValues: {
			title: item.title,
			content: item.content,
		},
		onSubmit: async ({ title, content }) => {
			const newTodo = {
				title,
				content,
			};
			updateTodo.mutate(newTodo);
		},
	});

react-query를 적용함으로서 getNewTodo라는 함수가 필요 없어지게 되었다. useQuery에 todo와 todo detail 정보를 불러오는 api호출에 필요한 todo.id를 key 값으로 설정을 해주었다.
Mutation을 통해 update를 해주었고, 성공시에는 refetch를 위하여 query를 무효화시켜주었다.

느낀점

이전에 팀프로젝트를 하면서 제일 고민하고 어려웠던게 실시간처리였다. 예를 들어, 댓글을 삭제하거나 추가할때 페이지 전체가 리렌더링 되지않으며 실시간 처리를 해주고 싶었는데 이때 많은 오류가 발생하였고 마음대로 되지 않았다. 주로 했던 방식이 위의 리팩토링 이전의 코드들 처럼 해당 정보를 불러온후 useState에 set해주는 함수를 만들고, 댓글을 작성 혹은 삭제시 성공하면 이 함수를 재호출하는 방식을 많이 사용했었다. 그때 react-query를 알았더라면 더 쉽게 해결 할 수 있지 않았을까.. useQuery와 useMutation만으로도 이렇게 간단히 처리가 되는게 신기하고 코드가 더 깔끔해진거 같다.

profile
내가 다시 볼려고 작성하는 블로그. 아직 열심히 공부중입니다 잘못된 내용이 있으면 댓글로 지적 부탁드립니다.

0개의 댓글