seunghyun Note
Recoil & react-hook-from (✈️ travel list) 본문
728x90
반응형
Recoil & react-hook-form
recoil 과 React-hook-form 을 이용한 간단한(?) to-do list를 복습해보려고 한다.
기본적인 파일구조
├── App.tsx ➡️ 주요 애플리케이션 컴포넌트가 위치
├── atoms.tsx ➡️ Recoil이나 다른 상태 관리 라이브러리를 사용할 때 상태들의 정의를 담당
├── components
│ ├── CreateTravel.tsx ➡️ 여행 항목을 생성하는 데 사용
│ ├── Travel.tsx ➡️ 여행 항목을 표시하고 해당 항목에 대한 작업을 수행
│ └── TravelList.tsx ➡️ 여행 항목들을 리스트 형태로 표시
└── index.tsx
일단 이 파일 구조를 이해하기 위해서는 원리를 그리면서 접근을 했다. (interface, atom을 일단 제외하고 component의 이동과 사용되는 부분들만 고려)
컴포넌트를 하나씩 보기 전에 useState와는 다르게 값이 컴포넌트마다 자유롭게 이동시킬 수 있는 Atoms.tsx부터 보자
Atoms.tsx
많이 신기한 atom의 세계
https://cojjangsh.tistory.com/149
- Atom은 상태(state)의 일부를 나타낸다. Atoms 은 어떤 컴포넌트에서나 읽고 쓸 수 있다. Atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 가져온다.
- 기본적인 key : "고유식별자" , default 는 []의 형태로 저장한다. (list를 ITravel타입의 상태)
- Recoil에서는 파생된 상태를 selector를 통해 관리한다.
- get: 현재 상태에서 파생된 값을 계산하는 로직을 포함한다.
- travelState를 가져와 이를 카테고리별로 필터링 -> TravelList.tsx에서
const [go, been, favorite] = useRecoilValue(travelSelector);
에서 사용된다.
import { atom, selector } from "recoil";
export interface ITravel {
text: string;
id: number;
category: "go" | "been" | "favorite";
}
export const travelState = atom<ITravel[]>({
key: "travel",
default: [],
});
export const travelSelector = selector({
key: "travelSelector",
get: ({ get }) => {
const travels = get(travelState);
return [
travels.filter((travel) => travel.category === "go"),
travels.filter((travel) => travel.category === "been"),
travels.filter((travel) => travel.category === "favorite"),
];
},
});
TravelList
- app에서 가장 먼저 연결되는 컴포넌트이다.
먼저 App.tsx를 보면 파일 내에 TravelList 밖에 없다.
//App.tsx
import TravelList from "./components/TravelList";
export default function App() {
return (
<main>
<TravelList />
</main>
);
}
- useRecoilValue를 통해 travelSelector(위에 atoms)에서 반환된 recoil 상태를 읽어온다.
- travelSelector에서 가져온 것을 배열의 형태로 저장한다(이건 각자의 방식으로..)
- CreateTravel 컴포넌트
- travelSelector에서 가져온 값들을 mapping해서 출력한다. (spread 연산자를 통해 배열 출력)
import { useRecoilValue } from "recoil";
import { travelSelector } from "../atoms";
import Travel from "./Travel";
import CreateTravel from "./CreateTravel";
function TravelList() {
const [go, been, favorite] = useRecoilValue(travelSelector);
return (
<div>
<h2>내가 가고 싶은 나라들</h2>
<CreateTravel />
<ul>
{go.map((travel) => (
<Travel key={travel.id} {...travel} />
))}
</ul>
<h2>내가 가본 나라들</h2>
<ul>
{" "}
{been.map((travel) => (
<Travel key={travel.id} {...travel} />
))}
</ul>
<h2>내가 좋아하는 나라들</h2>
{favorite.map((travel) => (
<Travel key={travel.id} {...travel} />
))}
</div>
);
}
export default TravelList;
CreateTravel
https://cojjangsh.tistory.com/150
- useForm을 사용한다~! (useForm 은 비교적 재밌고 실용적인듯..)
- 주석을 한번 보자
import { useForm } from "react-hook-form";
import { useSetRecoilState } from "recoil";
import { travelState } from "../atoms";
interface IForm {
travel: string;
}
export default function CreateTravel() {
const setTravels = useSetRecoilState(travelState);
const {
register, //register : register function은 많은 요청들을 해결해주고 handler 를 대신해준다.
handleSubmit, //기존 form에서는 event.preventDefault()를 이용해 form의 제출을 사용했다.
//useForm()에서는 handleSubmit을 사용한다.
setValue, //입력 필드의 값을 변경 -> 여기서는 비운다.
formState: { errors }, //formState : 전체 양식 상태에 대한 정보가 포함되어 있다. 다양한 옵션들이 있지만 결국 error를 찾기에 적합하다.
} = useForm<IForm>();
const handleValid = ({ travel }: IForm) => { //IForm 타입 설정(string)
setTravels((oldTravels) => [
{ text: travel, id: Date.now(), category: "go" },
...oldTravels,
]);
setValue("travel", ""); //필드 값을 지운당
};
return (
<form onSubmit={handleSubmit(handleValid)}>
<input
{...register("travel", {
required: "😠 required!",
})}
placeholder="이름"
type="text"
/>
<h4>{errors?.travel?.message}</h4> <!--위에서 formState를 사용해야죠~! error 출력-->
<button>가자</button>
</form>
);
}
Travel.tsx
사실 여기가 난관이였다.
내맘대로 지울수 없고... 버튼이 1개만 나와야하는데 헷갈렸다. ( 이 부분을 간단하게... 하려면 어떻게 해야할까?? 😅)
<li>
<span>{text}</span>
{category !== "favorite" && (
<button name="favorite" onClick={onClick}>
{category === "been" ? "👍" : "✅"}
</button>
)}
{category !== "been" && category !== "go" && (
<button name="been" onClick={onClick}>
👎🏻
</button>
)}
{category !== "go" && category !== "favorite" && (
<button name="go" onClick={onClick}>
❌
</button>
)}
{category === "go" && (
<button name="delete" onClick={onClick}>
🗑️
</button>
)}
</li>
그리고 atoms.. 고맙다 (useState였으면 계속 새롭게 할당하거나 한 컴포넌트에서 끝내야할텐데.. atoms 제대로 사용하고 있는거 같다.)
- Travel에서 props 구조로 나눠서 위에서 Travel 컴포넌트를 사용했다.
- useSetRecoilState를 사용한다(나는 계속 useRecoilState를 사용해서 오류가 나왔다..)
- travelState 아톰의 상태를 업데이트하는 함수인 setTravel을 가져온다
- 목록을 지울 delete를 추가해서 id가 다른 것들만 filtering 해준다. ( 그 외는 카테고리에 업데이트)
import React from "react";
import { useSetRecoilState } from "recoil";
import { ITravel, travelState } from "../atoms";
function Travel({ text, category, id }: ITravel) {
const setTravel = useSetRecoilState(travelState);
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name },
} = event;
if (name === "delete") {
setTravel((oldTravels) =>
oldTravels.filter((travel) => travel.id !== id)
);
} else {
setTravel((oldTravels) => {
const targetIndex = oldTravels.findIndex((travel) => travel.id === id);
const newTravel = { text, id, category: name as any };
return [
...oldTravels.slice(0, targetIndex),
newTravel,
...oldTravels.slice(targetIndex + 1),
];
});
}
};
return (
<li>
<span>{text}</span>
{category !== "favorite" && (
<button name="favorite" onClick={onClick}>
{category === "been" ? "👍" : "✅"}
</button>
)}
{category !== "been" && category !== "go" && (
<button name="been" onClick={onClick}>
👎🏻
</button>
)}
{category !== "go" && category !== "favorite" && (
<button name="go" onClick={onClick}>
❌
</button>
)}
{category === "go" && (
<button name="delete" onClick={onClick}>
🗑️
</button>
)}
</li>
);
}
export default Travel;
아직 해결은 되지 않아서.. 코드 리뷰를 받고 싶다.
728x90
반응형
'스터디 > REACT' 카테고리의 다른 글
React-hook-form (2) | 2024.03.12 |
---|---|
Recoil (0) | 2024.03.11 |
React 데이터 관리 및 라우팅 (1) | 2024.02.24 |