컴포넌트를 만들다 보면 반복되는 로직이 발생한다. (ex. input 관리)
이런 경우 커스텀 Hooks를 만들어서 반복되는 로직을 재사용할 수 있다.
useInputs.js
import { useState, useReducer, useCallback } from "react";
function reducer(state, action) {
// CHANGE
switch (action.type) {
case "CHANGE":
return {
...state,
[action.name]: action.value,
};
case "RESET":
return Object.keys(state).reduce((acc, current) => {
acc[current] = "";
return acc;
}, {});
default:
return state;
// RESET
}
}
function useInputs(initialForm) {
// 해당 input 폼에서 관리할 초기값
// const [form, setForm] = useState(initialForm);
const [form, dispatch] = useReducer(reducer, initialState);
const onChange = useCallback((e) => {
const { name, value } = e.target;
dispatch({
type: "CHANGE",
name,
value,
});
});
const reset = useCallback(() => {
dispatch({
type: "RESET",
});
}, []);
/*
const onChange = useCallback((e) => {
const { name, value } = e.target;
setForm((form) => ({ ...form, [name]: value }));
}, []);
// 의존하는 상태가 없음으로 빈 배열
// 폼 초기화 함수
const reset = useCallback(() => setForm(initialForm), [initialForm]);
// 파라미터로 가져온 걸 사용하고 있으니까
*/
return [form, onChange, reset];
}
export default useInputs;
만든 useInputs
Hook을 App.js에서 사용하기
App.js
import React, { useReducer, useRef, useMemo, useCallback } from "react";
import CreateUser from "./CreateUser";
import UserList from "./UserList";
import useInputs from "./useInputs";
// useMemo: 특정값이 바뀌었을 때만 특정 함수를 실행해서 연산하도록 처리
function countActiveUsers(users) {
console.log("활성 사용자 수 세는 중");
return users.filter((user) => user.active).length;
// active가 true인 사용자 필터링해 수를 연산해서 가져옴
}
// 1) 앱 컴포넌트에서 사용할 초기상태를 컴포넌트 바깥에 선언해주기
const initialState = {
users: [
{
id: 1,
username: "su",
email: "susu@gmail.com",
active: true,
},
{
id: 2,
username: "liz",
email: "lili@gmail.com",
active: false,
},
{
id: 3,
username: "ro",
email: "ro@gmail.com",
active: false,
},
],
};
// 2) reducer 함수의 틀 만들기
function reducer(state, action) {
switch (action.type) {
case "CREATE_USER":
return {
inputs: initialState.inputs, // input 공백값 반환
users: state.users.concat(action.user),
};
case "TOGGLE_USER":
return {
...state,
users: state.users.map((user) =>
user.id === action.id
? {
...user,
active: !user.active,
}
: user
),
}; //
case "REMOVE_USER":
return {
...state, // 복사
users: state.users.filter((user) => user.id !== action.id),
};
default:
return state;
}
}
function App() {
// 3) 내부에서 useReducer 함수 선언
const [state, dispatch] = useReducer(reducer, initialState);
const [form, onChange, reset] = useInputs({
username: "",
email: "",
});
const { username, email } = form;
// 6-1) useRef 사용 아이디값 부여
const nextId = useRef(4);
// 4) 비구조화 할당으로 users, inputs 내용 추출
const { users } = state;
// 5) onChange 함수 구현
// 6) onCreate 구현
const onCreate = useCallback(() => {
dispatch({
type: "CREATE_USER",
user: {
id: nextId.current,
username,
email,
},
});
nextId.current += 1;
reset();
}, [username, email, reset]); // 함수에서 사용하는 상태 혹은 props, deps 배열에 포함
// 7) onToggle
const onToggle = useCallback((id) => {
dispatch({
type: "TOGGLE_USER",
id,
});
}, []);
// 8) onRemove
const onRemove = useCallback((id) => {
dispatch({
type: "REMOVE_USER",
id, // id값만 비교해주니까!
});
}, []);
// 9) 활성사용자 수
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성 사용자 수 : {count} </div>
</>
);
}
export default App;