useEffect hook을 이용한 로직은 반복되는 로직이 많습니다. 특히 API를 통해 데이터를 받아와 처리하는 로직은 반복적일 수밖에 없습니다. 이러한 로직을 custom hook으로 만들어 분리하고 필요한 컴포넌트마다 적용을 한다면 컴포넌트들을 좀 더 직관적으로 관리할 수 있을 것입니다.
App 컴포넌트에 들어있는 useEffect hook 로직을 util 폴더 내의 hooks.js 파일에 분리해 보고, 직관적으로 파악할 수 있도록 hooks.js의 이름도 변경해봅시다.
처음 코드
//App.js
import "./styles.css";
import { useEffect, useState } from "react";
export default function App() {
const [data, setData] = useState();
useEffect(() => {
fetch("data.json", {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
})
.then((response) => {
return response.json();
})
.then((myJson) => {
setData(myJson);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="App">
<h1>To do List</h1>
<div className="todo-list">
{data &&
data.todo.map((el) => {
return <li key={el.id}>{el.todo}</li>;
})}
</div>
</div>
);
}
//util/hooks.js
//app.js에서 함수를 가져와서 Custom hook을 만들어 봅시다.
//hooks.js의 이름도 알맞게 바꿔주세요.
const hooks = (fetchUrl) => {};
export default hooks;
바꾼 코드
//App.js
import useFetch from "./util/useFetch"; //hooks.js에서 이름 변경
import "./styles.css";
export default function App() {
const fetchData = useFetch("data.json");
return (
<div className="App">
<h1>To do List</h1>
<div className="todo-list">
{fetchData &&
fetchData.todo.map((el) => {
return <li key={el.id}>{el.todo}</li>;
})}
</div>
</div>
);
}
//util/hooks.js
import { useEffect, useState } from "react";
const useFetch = (fetchUrl) => {
const [data, setData] = useState();
useEffect(() => {
fetch(fetchUrl, {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
})
.then((response) => {
return response.json();
})
.then((myJson) => {
setData(myJson);
})
.catch((error) => {
console.log(error);
});
}, [fetchUrl]);
//loading, error
return data;
};
export default useFetch;
input 또한 반복적으로 사용되는 로직을 가지고 있는 컴포넌트입니다. 이런 식으로 앱 내에 반복적으로 사용 되고 관리되는 로직은 많습니다. (input, form, button 등) input도 지금은 2개 정도 있어 관리가 크게 요구되지 않습니다. 그러나 후에 회원가입 컴포넌트를 만들거나, 앱의 볼륨이 커지게 된다면 input은 굉장히 많이 사용되는 UI 중 하나이기 때문에 반복되는 로직을 관리 해야 하는 필요성이 생깁니다. 이런 컴포넌트 또한 custom hook을 이용하여 로직을 분리하여 관리할 수 있습니다. 컴포넌트 내에 반복되는 로직을 분리를 해 관리를 하면 컴포넌트들을 좀 더 깔끔하게 관리할 수 있습니다.
App 컴포넌트에 들어 있는 input을 Input 컴포넌트로 바꾼 후, custom hook을 이용하여 input의 로직을 분리해봅시다.
처음 코드
//App.js
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./component/Input";
import "./styles.css";
export default function App() {
//input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
//until 폴더의 useInput.js 파일이 만들어져 있습니다.
const [firstNameValue, setFirstNameValue] = useState("");
const [lastNameValue, setLastNameValue] = useState("");
const [nameArr, setNameArr] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
setNameArr([...nameArr, `${firstNameValue} ${lastNameValue}`]);
};
return (
<div className="App">
<h1>Name List</h1>
<div className="name-form">
<form onSubmit={handleSubmit}>
<div className="name-input">
<label>성</label>
<input
value={firstNameValue}
onChange={(e) => setFirstNameValue(e.target.value)}
type="text"
/>
</div>
<div className="name-input">
<label>이름</label>
<input
value={lastNameValue}
onChange={(e) => setLastNameValue(e.target.value)}
type="text"
/>
</div>
<button>제출</button>
</form>
</div>
<div className="name-list-wrap">
<div className="name-list">
{nameArr.map((el, idx) => {
return <p key={idx}>{el}</p>;
})}
</div>
</div>
</div>
);
}
//util/useInput.js
//이 곳에 input custom hook을 만들어 보세요.
//return 해야 하는 값은 배열 형태의 값이어야 합니다.
function useInput() {
return [];
}
export default useInput;
//component/Input.js
function Input() {
//input을 컴포넌트로 만들어봅니다.
//input도 컴포넌트도 만들게 되면 로직을 조금 더 고민을 해야 합니다.
//고민을 한 번 해보고, 시도 해보세요!
}
export default Input;
바꾼 코드
//CustomInputExcercise.js
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./components/Input";
import "./input.css";
const CustomInputExcercise = () => {
const [firstValue, firstChangeHandler, firstReset] = useInput("");
const [lastValue, lastChangeHandler, lastReset] = useInput("");
const [nameArr, setNameArr] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
setNameArr([...nameArr, `${firstValue} ${lastValue}`]);
firstReset();
lastReset();
};
return (
<div className="App">
<h1>Name List</h1>
<div className="name-form">
<form onSubmit={handleSubmit}>
<Input
value={firstValue}
onChangeHandler={firstChangeHandler}
label="성"
/>
<Input
value={lastValue}
onChangeHandler={lastChangeHandler}
label="이름"
/>
<button>제출</button>
</form>
</div>
<div className="name-list-wrap">
<div className="name-list">
{nameArr.map((el, idx) => {
return <p key={idx}>{el}</p>;
})}
</div>
</div>
</div>
);
}
export default CustomInputExcercise;
//util/useInput.js
import { useState } from "react";
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
const changeHandler = (e) => {
setValue(e.target.value);
};
const submit = () => {
setValue(initialValue);
};
return [value, changeHandler, submit];
}
export default useInput;
//component/Input.js
function Input({ value, onChangeHandler, label }) {
return (
<div className="name-input">
<label>{label}</label>
<input value={value} onChange={(e) => onChangeHandler(e)} type="text" />
</div>
);
}
export default Input;
//CustomInputExcercise.js
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./components/Input";
import "./input.css";
const CustomInputExcercise = () => {
const [firstName, firstBind, firstReset] = useInput("");
const [lastName, lastBind, lastReset] = useInput("");
const [nameArr, setNameArr] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
setNameArr([...nameArr, `${firstName} ${lastName}`]);
firstReset();
lastReset();
};
return (
<div className="App">
<h1>Name List</h1>
<div className="name-form">
<form onSubmit={handleSubmit}>
<Input
label={"성"}
bind={firstBind}
/>
<Input
label={"이름"}
bind={lastBind}
/>
<button>제출</button>
</form>
</div>
<div className="name-list-wrap">
<div className="name-list">
{nameArr.map((el, idx) => {
return <p key={idx}>{el}</p>;
})}
</div>
</div>
</div>
);
}
export default CustomInputExcercise;
//useInput.js
import { useCallback, useState } from "react";
function useInput(initialValue) {
const [value, setValue] = useState(initialValue);
/*
//1번째 방법
const changeHandler = (e) => {
setValue(e.target.value);
};*/
/*
2번째 방법
const changeHandler = useCallback((e) => {
setValue(e.target.value);
}, []);
*/
/*const submit = () => {
setValue(initialValue);
};*/
//3번째 방법
const bind = {
value,
onChange: useCallback((e) => {
const { value } = e.target;
setValue(value);
}, [])
};
const submit = useCallback(() =>
setValue(initialValue), [initialValue]);
return [value, bind, submit];
}
export default useInput;
//Input.js
function Input({ label, bind }) {
return (
<div className="name-input">
<label>{label}</label>
<input type="text" required {...bind} />
</div>
);
}
export default Input;