[React] React query 를 custom hook으로 만들기

임홍원·2023년 12월 18일
2
post-thumbnail

기존에 작성했던 React query 코드를 Custom Hook으로 작성한 걸 기억하기 위해 이 글을 남긴다.

기존 코드

기존에 있던 코드는 컴포넌트 안에 React query 관련한 코드가 있어서 코드 분리의 필요성을 느끼게 하는 코드였다.

// TodoItem.tsx
const TodoItem = (props: { item: Todo }) => {
	const { id, title, content, isDone } = props.item;
	const queryClient = useQueryClient();

	const updateMutate = useMutation({
		mutationKey: ['todos'],
		mutationFn: updateTodo,
		onSuccess: () => {
			queryClient.invalidateQueries({
				queryKey: ['todos'],
			});
		},
	});

	const isDoneHandler = (id: string): void => {
		updateMutate.mutate({
			...props.item,
			isDone: !isDone,
		});
	};

	const deleteMutate: UseMutationResult<Todo, Error, string, unknown> =
		useMutation({
			mutationKey: ['todos'],
			mutationFn: removeTodo,
			onSuccess: () => {
				queryClient.invalidateQueries({
					queryKey: ['todos'],
				});
			},
		});

	const deleteTodoHandler = (id: string): void => {
		deleteMutate.mutate(id);
	};

	return (
		<Container $done={isDone}>
			<TitleContainer>{title}</TitleContainer>
			<ContentContainer>{content}</ContentContainer>
			<ButtonContainer>
				<DoneButton
					onClick={() => {
						isDoneHandler(id);
					}}
				>
					{isDone ? '취소' : '완료'}
				</DoneButton>
				<DeleteButton
					onClick={() => {
						deleteTodoHandler(id);
					}}
				>
					삭제
				</DeleteButton>
			</ButtonContainer>
		</Container>
	);
};

export default TodoItem;
// NewTodo.tsx
const NewTodo = () => {
	const queryClient = useQueryClient();
	const [newTodoTitle, titleHandler] = useInput<string>('');
	const [newTodoContent, contentHandler] = useInput<string>('');

	const addMutation: UseMutationResult<Todo, Error, Todo, unknown> =
		useMutation({
			mutationKey: ['todos'],
			mutationFn: addTodo,
			onSuccess: () => {
				queryClient.invalidateQueries({ queryKey: ['todos'] });
			},
		});

	const submitHandler = (e: FormEvent): void => {
		e.preventDefault();
		addMutation.mutate({
			id: uuid(),
			title: newTodoTitle,
			content: newTodoContent,
			isDone: false,
		});
	};

	return (
		<TodoForm onSubmit={submitHandler}>
			<TodoTitleInput
				onChange={titleHandler}
				placeholder='제목'
				value={newTodoTitle}
				required
			/>
			<TodoContentInput
				onChange={contentHandler}
				placeholder='내용'
				value={newTodoContent}
				required
			/>
			<TodoButton>등록하기</TodoButton>
		</TodoForm>
	);
};

export default NewTodo;

위 두 코드를 커스텀 훅으로 빼서 따로 관리하면 어떨까? 라는 생각이 들었고, useTodoQuery라는 훅으로 따로 빼 만들었다.

Custom Hook

// useTodoQuery.ts
import {
	UseMutationResult,
	useMutation,
	useQuery,
	useQueryClient,
} from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { addTodo, getTodoData, removeTodo, updateTodo } from '../apis/todoApi';

const useTodoQuery = () => {
	const queryClient = useQueryClient();

	const { data } = useQuery<Todo[], AxiosError>({
		queryKey: ['todos'],
		queryFn: getTodoData,
	});

	const addMutation: UseMutationResult<Todo, Error, Todo, unknown> =
		useMutation({
			mutationKey: ['todos'],
			mutationFn: addTodo,
			onSuccess: () => {
				queryClient.invalidateQueries({ queryKey: ['todos'] });
			},
		});

	const updateMutatation = useMutation({
		mutationKey: ['todos'],
		mutationFn: updateTodo,
		onSuccess: () => {
			queryClient.invalidateQueries({
				queryKey: ['todos'],
			});
		},
	});

	const deleteMutatation: UseMutationResult<Todo, Error, string, unknown> =
		useMutation({
			mutationKey: ['todos'],
			mutationFn: removeTodo,
			onSuccess: () => {
				queryClient.invalidateQueries({
					queryKey: ['todos'],
				});
			},
		});

	return {
		todos: data,
		addTodo: addMutation,
		updateTodo: updateMutatation,
		removeTodo: deleteMutatation,
	};
};

export default useTodoQuery;

수정 후 코드

// TodoItem.tsx
const TodoItem = (props: { item: Todo }) => {
	const { id, title, content, isDone } = props.item;
	const { updateTodo, removeTodo } = useTodoQuery();

	const isDoneHandler = (id: string): void => {
		updateTodo.mutate({
			...props.item,
			isDone: !isDone,
		});
	};
// NewTodo.tsx
const NewTodo = () => {
	const [newTodoTitle, titleHandler, titleReset] = useInput<string>('');
	const [newTodoContent, contentHandler, contentReset] = useInput<string>('');
	const { addTodo } = useTodoQuery();

	const submitHandler = (e: FormEvent): void => {
		e.preventDefault();
		addTodo.mutate({
			id: uuid(),
			title: newTodoTitle,
			content: newTodoContent,
			isDone: false,
		});
        titleReset();
        contentReset();
	};

기존 코드에 비해 코드가 훨씬 간결해지고 코드의 가독성이 올라갔다.
막연하게 Custom Hook을 작성하는것에 대한 두려움과 에러가 나면 어떡하지 라는 생각이 기존에는 존재했지만, 한번 작성하고 나니 왜 이렇게 좋은걸 지금까지 활용하지 못했나 하는 생각이 들었다.

그냥 함수를 따로 빼내어 필요한 기능만 모듈처럼 불러오는것과 비슷하다는 것을 깨달았다.

앞으로 Custom Hook을 더 적극적으로 활용해야겠다.

2개의 댓글

comment-user-thumbnail
2023년 12월 18일

오....배워갑니다.....

1개의 답글