React | custom hook (useAsync 훅 만들기)
Custom Hook?
- 리액트가 제공하는 hooks를 이용해 커스텀 훅을 만들 수 있다
- 커스텀 훅은 로직을 재사용 할 수 있다
- 커스텀 훅의 이름은 리액트 내장 훅처럼
use
로 시작하는 것이 좋다
A custom Hook is a JavaScript function whose name starts with use and that may call other Hooks.
출처: https://reactjs.org/docs/hooks-custom.html
useAsync hook
많이들 만들어 사용하는 커스텀 훅 중에 하나인 useAsync
훅을 만들어본다
API 요청에 유용하게 사용된다!
일단 useReducer
와 useEffect
, useCallback
을 import 한다.
import { useReducer, useEffect, useCallback } from 'react';
useReducer
를 사용하기 위해 reducer
를 작성해준다.initailState
는 이렇게 따로 선언해도 좋고, useReduer
의 두 번째 인자 값에 바로 작성해도 좋다.
loading은 api요청의 응답을 받기 전까지 true를 유지한다.
data는 api요청의 응답 값 이다.
error는 api요청에 실패했을 경우 에러를 저장한다.
const initialState = {
loading: false,
data: null,
error: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'LOADING':
return {
loading: true,
data: null,
error: null,
};
case 'SUCCESS':
return {
loading: false,
data: action.data,
error: null,
};
case 'ERROR':
return {
loading: false,
data: null,
error: action.error,
};
case 'CLEAR':
return initialState;
default:
return state;
}
};
그리고 본격적으로 useAsync
훅을 작성
인자 값으로 받는 callback
은 api요청을 할 함수를,
immediate
는 해당 함수를 즉시 실행할지 여부를 전달받는다. 기본값은 false
로 설정.
useCallback
는 함수를 재사용 할 수 있는 리액트에서 제공하는 내장 훅이다. 의존성 배열의 callback값의 변경 여부를 확인한다.
const useAsync = (callback, immediate = false) => {
const [state, dispatch] = useReducer(reducer, initialState);
const execute = useCallback(
async (...args) => {
dispatch({ type: 'LOADING' });
try {
const data = await callback(options, ...args);
dispatch({ type: 'SUCCESS', data });
return true;
} catch (e) {
dispatch({ type: 'ERROR', error: e });
}
},
[callback]
);
useEffect(() => {
immediate && execute();
return () => dispatch({ type: 'CLEAR' });
}, [immediate, execute]);
return { ...state, execute };
};
export default useAsync;
return [state, execute]로 반환할 수도 있다. (취향차이)
사용해보기!
로그인을 한다고 할 때.. 로그인을 위한 서버에 요청하는 부분을 useAsync
를 사용해보자.
리액트와 api요청을 위한 axios
, 그리고 방금 작성한 useAsync
훅을 import한다.
import React, { useState } from 'react';
import axios from 'axios';
import useAsync from '../hooks/useAsync';
아까 useAsync
훅에서 {...state, execute }
를 반환해주었기 때문에,
{ loading, error, execue: login }
으로 받을 수 있다.
execute: login
은 execute대신 login으로 사용하겠다.. 뭐 이렇게 이해하면 된다..
// try/catch 생략..
const apiRequesLogin = async (data) => {
const response = await axios.post('http://...(생략)');
return response.data;
}
const LoginForm = () => {
const { loading, error, data, execute: login } = useAsync(apiRequesLogin);
const [formInput, setFromInput] = useState({
id: '',
password: '',
});
const { account, password } = formInput;
const onChange = (e) => {
setFromInput({
...formInput,
[e.target.name]: e.target.value,
});
};
const onSubmit = async (e) => {
e.preventDefault();
const result = await login(formInput);
console.log("apiRequestLogin response.data = ", data);
if (result) {
// 나중엔 이렇게 react-router-dom의 history를 이용해서 페이지를 이동해주면 된다.
// history.push('/user');
}
};
if (loading) {
return <h1>loading...</h1>;
}
if (error) {
return <h1>{error}</h1>;
}
return (
<form onSubmit={onSubmit}>
<input name="id" type="text" value={id} onChange={onChange} />
<input
name="password"
type="password"
value={password}
onChange={onChange}
/>
<button type="submit">LOGIN</button>
</form>
);
};
export default LoginForm;