React JS에서 사용할 수 있는 상태 관리 라이브러리
Index.tsx에 있는 ThemeProvider를 App component로 옮기자
// App.tsx
function App() {
return (
<>
<ThemeProvider theme={theme}>
<GlobalStyle />
<Router />
<ReactQueryDevtools initialIsOpen={true} />
</ThemeProvider>
</>
);
}
export default App;
테마를 추가해보자
// Theme.ts
import { DefaultTheme } from 'styled-components';
export const darkTheme: DefaultTheme = {
bgColor: '#2f3640',
textColor: '#f5f6fa',
accentColor: '#9c88ff'
};
export const lightTheme: DefaultTheme = {
bgColor: 'whitesmoke',
textColor: 'black',
accentColor: '#9c88ff'
};
현재 모드 state를 만들자
const [isDark, setIsDark] = useState(false);
isDark 토글 함수를 만들자
const toggleDark = () => setIsDark((current) => !current);
버튼을 만들고, 클릭 시 함수를 집어넣자
return (
<>
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<button onClick={toggleDark}>Toggle Mode</button>
<GlobalStyle />
<Router />
<ReactQueryDevtools initialIsOpen={true} />
</ThemeProvider>
</>
);
Router에 인터페이스를 추가하고 Coins에 전달하자
interface IRouterProps {
toggleDark: () => void;
}
function Router({ toggleDark }: IRouterProps) {
return (
<BrowserRouter>
<Switch>
<Route path='/:coinId'>
<Coin />
</Route>
<Route path='/'>
<Coins toggleDark={toggleDark} />
</Route>
</Switch>
</BrowserRouter>
);
}
Coins에서 toggleDark 함수를 전달받자
interface ICoinsProps {
toggleDark: () => void;
}
function Coins({ toggleDark }: ICoinsProps) {
const { isLoading, data } = useQuery<ICoin[]>('allCoins', fetchCoins);
return (
<Container>
<Helmet>
<title>코인</title>
</Helmet>
<Header>
<Title>코인</Title>
<button onClick={toggleDark}>Toggle Dark Mode</button>
App → Router로 isDark를 보내주자
function App() {
const [isDark, setIsDark] = useState(false);
const toggleDark = () => setIsDark((current) => !current);
return (
<>
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<Router isDark={isDark} toggleDark={toggleDark} />
Router → Coin으로 isDark를 보내주자
interface IRouterProps {
toggleDark: () => void;
isDark: boolean;
}
function Router({ toggleDark, isDark }: IRouterProps) {
return (
<BrowserRouter>
<Switch>
<Route path='/:coinId'>
<Coin isDark={isDark} />
</Route>
<Route path='/'>
<Coins toggleDark={toggleDark} />
</Route>
</Switch>
</BrowserRouter>
);
}
export default Router;
Coin → Chart로 isDark를 보내주자
interface ICoinProps {
isDark: boolean;
}
function Coin({ isDark }: ICoinProps) {
...
..
.
<Switch>
<Route path={`/:coinId/price`}>
<Price />
</Route>
<Route path={`/:coinId/chart`}>
<Chart isDark={isDark} coinId={coinId} />
</Route>
</Switch>
Chart에서 isDark를 받아 차트에 적용하자
interface CharProps {
coinId: string;
isDark: boolean;
}
function Chart({ coinId, isDark }: CharProps) {
...
..
.
<ApexChart
type='line'
series={[{ name: 'Price', data: data?.map((price) => price.close) ?? [] }]}
options={{
theme: { mode: isDark ? 'dark' : 'light' },
옮기고 옮기고 또 옮기고… 반복 작업 발생, 복잡.. ⇒
상태관리를 사용하자
component를 atom의 value로 연결하자
상태관리 미사용
상태관리 사용
기존에서는 isDark가 필요없는 Router, Coin도 전달을 하기 위해 isDark를 가져가야 했다.
허나, 상태관리는 독립적인 공간에 컴포넌트(atom)를 저장해놔 필요한 애들만 쓸 수 있게 해준다!
Recoil 라이브러리 설치 : npm install recoil
Index.tsx에서 RecoilRoot로 감싸주자
ReactDOM.render(
<React.StrictMode>
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</RecoilRoot>
</React.StrictMode>,
document.getElementById('root')
);
atom 정의 : atom({ key : 유일한 이름, default : 기본값})
// atom.ts -> src 폴더 내
import { atom } from 'recoil';
export const isDarkAtom = atom({
key: 'isDark',
default: false
});
atom을 App과 연결하자
useRecoilValue
hook 사용 ⇒ useRecoilValue(가져오고자 하는 atom명)
import { useRecoilValue } from 'recoil';
import { isDarkAtom } from './atoms';
function App() {
const isDark = useRecoilValue(isDarkAtom);
return (
<>
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<Router />
<ReactQueryDevtools initialIsOpen={true} />
</ThemeProvider>
</>
);
}
마찬가지로 atom을 Chart에서 사용하자
atom의 value를 수정하자 ⇒ Coins에서 모드 변경 함수를 만들자
useSetRecoilState
hook 사용 ⇒ useSetRecoilState(수정하고자 하는 atom명)
// Coins.tsx
const setDarkAtom = useSetRecoilState(isDarkAtom);
const toggleDarkAtom = () => setDarkAtom((prev) => !prev);
<Header>
<Title>코인</Title>
<button onClick={toggleDarkAtom}>Toggle Mode</button>
</Header>
To do 리스트를 빌드해보자
지금까지 했던 비트코인 페이지는 다 지웁니다!
ToDoList.tsx 생성하자
function ToDoList() {
return null;
}
export default ToDoList;
App에 ToDoList를 렌더링하자
function App() {
return (
<>
<GlobalStyle />
<ToDoList />
</>
);
}
export default App;
Form을 작성하자
// ToDoList.tsx
import { useState } from 'react';
function ToDoList() {
const [toDo, setToDo] = useState('');
const onChange = (event: React.FormEvent<HTMLInputElement>) => {
const {
currentTarget: { value }
} = event;
setToDo(value);
};
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(toDo);
};
return (
<div>
<form onSubmit={onSubmit}>
<input onChange={onChange} value={toDo} placeholder='Write to do' />
<button>Add</button>
</form>
</div>
);
}
export default ToDoList;
⇒ input이 여러개면 같은 함수, state를 여러 개 만들어야함..
react-hook-form에 대해 배우자
설치하자 : npm install react-hook-form
useForm을 import 하자
import { useForm } from 'react-hook-form';
**useForm
hook을 사용하자**
register 함수 : onChange 이벤트 핸들러, props, setstate를 대체
function ToDoList() {
const { register } = useForm();
console.log(register('To do'));
return (
<div>
<form>
<input placeholder='Write to do' />
<button>Add</button>
</form>
</div>
);
}
export default ToDoList;
+) onBlur ? → input 창 밖을 클릭 시
register
가 반환하는 객체를 input의 props로 넘겨주자
function ToDoList() {
const { register } = useForm();
return (
<div>
<form>
<input {...register('toDo')} placeholder='Write to do' />
<button>Add</button>
</form>
</div>
);
}
⇒ 아직은 잘 작동하는지 확인 불가능하다
useForm
의 watch
를 사용하자
watch 함수 : 입력한 form 입력값들의 변화를 관찰할 수 있게 해주는 함수
function ToDoList() {
const { register, watch } = useForm();
console.log(watch());
return (
<div>
<form>
<input {...register('toDo')} placeholder='Write to do' />
<button>Add</button>
</form>
</div>
);
}
form의 onSubmit을 react-form-hook을 사용해 대체해보자
handleSubmit : validation, preventDefault 등 담당
⇒ handleSubmit(데이터가 유효할 때 실행되는 함수, 데이터가 유효하지 않을 때 실행되는 함수)
function ToDoList() {
const { register, watch, handleSubmit } = useForm();
const onValid = (data: any) => {
console.log(data);
};
return (
<div>
<form onSubmit={handleSubmit(onValid)}>
<input {...register('toDo', { required: true })} placeholder='Write to do' />
<button>Add</button>
</form>
</div>
);
}
<input {...register('toDo', { required: true, minLength: 10 })} placeholder='Write to do' />
const { register, watch, handleSubmit, formState } = useForm();
console.log(formState.errors);
단순히 에러가 있다는 사실뿐만이 아니라, 어떤 종류의 에러가 생겼는지도 알 수 있다.
function ToDoList() {
const { register, watch, handleSubmit, formState } = useForm();
const onValid = (data: any) => {
console.log(data);
};
console.log(formState.errors);
return (
<div>
<form style={{ display: 'flex', flexDirection: 'column' }} onSubmit={handleSubmit(onValid)}>
<input
{...register('toDo', {
required: 'To-Do is required',
minLength: {
value: 5,
message: 'Too Short'
}
})}
placeholder='Write to do'
/>
<button>Add</button>
</form>
</div>
);
}
정규식(RegExp)을 사용해 validation을 구현해보자
바로 값을 보내자
<input
{...register('email', {
required: 'Email is required',
pattern: /^[A-Za-z0-9._%+-]+@naver.com/
})}
placeholder='Write to do'
/>
객체에다 넣어서 보내자
<input
{...register('email', {
required: 'Email is required',
pattern: { value: /^[A-Za-z0-9._%+-]+@naver.com/, message: 'Only naver.com emails allowed' }
})}
placeholder='Write Email'
/>
<span>{errors?.email?.message}</span>
data → any로 해놓는 건 타입스크립트의 보호기능을 사용하기에 좋지 않다
form의 인터페이스를 만들자
interface IForm {
toDo: string;
email: string;
}
const {
register,
watch,
handleSubmit,
formState: { errors }
} = useForm<IForm>();
// 필수가 아니라면 ?를 붙이자 -> ex) nickname?: string;
어떻게 에러를 발생시킬 수 있는지 알아보고, validation을 만들어보자
validation의 데이터 타입을 any에서 IForm으로 변경하자
const onValid = (data: IForm) => {
console.log(data);
};
setError를 가져오자
⇒ setError
: 특정한 에러를 발생시키게 해준다.
const {
register,
watch,
handleSubmit,
formState: { errors },
setError
} = useForm<IForm>({ defaultValues: { email: '@naver.com' } });
password와 password1을 비교해 setError를 발생시키자
const onValid = (data: IForm) => {
if (data.password !== data.password1) {
setError('password1', { message: 'Password are not the same' });
}
};
form 인터페이스에 항목을 추가하자
interface IForm {
toDo: string;
email: string;
password: string;
password1: string;
extraError?: string;
}
에러를 추가하자
const onValid = (data: IForm) => {
if (data.password !== data.password1) {
setError('password1', { message: 'Password are not the same' });
}
setError('extraError', { message: 'Server Offline.' });
};
에러를 보여주자
<span>{errors?.extraError?.message}</span>
+) setError는 form에서 특정 input에 강제로 포커싱할 수 있다.
const onValid = (data: IForm) => {
if (data.password !== data.password1) {
setError('password1', { message: 'Password are not the same' }, { shouldFocus: true });
}
setError('extraError', { message: 'Server Offline.' });
};
→ toDo에 alcohol
이 포함되어 있으면 금지시키자
validate 옵션을 사용하자
⇒ validate : (현재 쓰여지고 있는 값)
<input
{...register('toDo', {
required: 'To-Do is required',
minLength: {
value: 5,
message: 'Too Short'
},
validate: (value) => !value.includes('alcohol')
})}
placeholder='Write to do'
/>
사용자에게 에러메시지를 띄우자
<input
{...register('toDo', {
required: 'To-Do is required',
minLength: {
value: 5,
message: 'Too Short'
},
validate: (value) => (value.includes('alcohol') ? 'No Alcohol allowed' : true)
})}
placeholder='Write to do'
/>
<span>{errors?.toDo?.message}</span>
validate 객체로 변경해보자
⇒ 규칙 여러개를 생성할 수 있다
<input
{...register('toDo', {
required: 'To-Do is required',
minLength: {
value: 5,
message: 'Too Short'
},
validate: { noAlcol: (value) => (value.includes('alcohol') ? 'No Alcohol allowed' : true) }
})}
placeholder='Write to do'
/>
+) Submit 후 값을 초기화하자
const { register, handleSubmit, setValue } = useForm<IForm>();
const handleValid = (data: IForm) => {
console.log('add to do', data.toDo);
setValue('toDo', '');
};
toDoState를 만들자
const toDoState = atom({
key: 'toDo',
default: []
});
toDoState 값을 가져오자 ⇒ useRecoilValue
const value = useRecoilValue(toDoState);
toDoState 값을 수정하자 ⇒ useSetRecoilState
const modFn = useSetRecoilState(toDoState);
+) 한번에 값과 수정하기를 해보자
⇒ useRecoilState
사용 : value와 modifier 함수를 반환한다.
const [toDos, setToDos] = useRecoilState(toDoState);
TypeScript에게 배열의 타입을 알려주자
interface IToDo {
text: string;
category: 'TO_DO' | 'DOING' | 'DONE';
}
const toDoState = atom<IToDo[]>({
key: 'toDo',
default: []
});
⇒ 카테고리는 세가지 문자열만 갖게 된다!
const handleValid = ({ toDo }: IForm) => {
console.log('add to do', toDo);
setToDos((oldToDos) => [{ text: toDo, category: 'TO_DO' }, ...oldToDos]);
setValue('toDo', '');
};
hello 라고 입력하면?
<ul>
{toDos.map((toDo) => (
<li key={toDo.id}>{toDo.text}</li>
))}
</ul>
코드를 리펙토링 해보자
CreateToDo.tsx 생성
import { useForm } from 'react-hook-form';
import { useSetRecoilState } from 'recoil';
import { toDoState } from '../atom';
interface IForm {
toDo: string;
}
function CreateToDo() {
const setToDos = useSetRecoilState(toDoState);
const { register, handleSubmit, setValue } = useForm<IForm>();
const handleValid = ({ toDo }: IForm) => {
setToDos((oldToDos) => [{ text: toDo, id: Date.now(), category: 'TO_DO' }, ...oldToDos]);
setValue('toDo', '');
};
return (
<form onSubmit={handleSubmit(handleValid)}>
<input {...register('toDo', { required: 'Please write a To Do' })} placeholder='Write to do' />
<button>Add</button>
</form>
);
}
export default CreateToDo;
ToDo.tsx 생성
import { IToDo } from '../atom';
function ToDo({ text }: IToDo) {
return <li>{text}</li>;
}
export default ToDo;
ToDoList.tsx 수정
import { useRecoilValue } from 'recoil';
import { toDoState } from '../atom';
import CreateToDo from './CreateToDo';
import ToDo from './ToDo';
function ToDoList() {
const toDos = useRecoilValue(toDoState);
return (
<div>
<h1>To Dos</h1>
<hr />
<CreateToDo />
<ul>
{toDos.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</ul>
</div>
);
}
export default ToDoList;
카테고리를 위해 ToDo.tsx에 버튼을 추가하자
import { IToDo } from '../atom';
function ToDo({ text }: IToDo) {
return (
<li>
<span>{text}</span>
<button>ToDo</button>
<button>Doing</button>
<button>Done</button>
</li>
);
}
export default ToDo;
사용자들이 버튼을 이용해서 toDo의 카테고리를 바꿀 수 있게 하자
카테고리를 체크하자
import { IToDo } from '../atom';
function ToDo({ text, category }: IToDo) {
return (
<li>
<span>{text}</span>
{category !== 'TO_DO' && <button>ToDo</button>}
{category !== 'DOING' && <button>Doing</button>}
{category !== 'DONE' && <button>Done</button>}
</li>
);
}
export default ToDo;
카테고리를 바꾸는 함수를 만들자
방법 1
💡 인자가 있는 함수는 **() ⇒ 함수(인자)** 이런 형태로 쓴다!import { IToDo } from '../atom';
function ToDo({ text, category }: IToDo) {
const onClick = (newCategory: IToDo['category']) => {
console.log('i wanna to', newCategory);
};
return (
<li>
<span>{text}</span>
{category !== 'TO_DO' && <button onClick={() => onClick('TO_DO')}>ToDo</button>}
{category !== 'DOING' && <button onClick={() => onClick('DOING')}>Doing</button>}
{category !== 'DONE' && <button onClick={() => onClick('DONE')}>Done</button>}
</li>
);
}
export default ToDo;
방법 2
function ToDo({ text, category }: IToDo) {
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
console.log('i wanna go', event.currentTarget.name);
};
return (
<li>
<span>{text}</span>
{category !== 'TO_DO' && (
<button name='TO_DO' onClick={onClick}>
ToDo
</button>
)}
{category !== 'DOING' && (
<button name='DOING' onClick={onClick}>
Doing
</button>
)}
{category !== 'DONE' && (
<button name='DONE' onClick={onClick}>
Done
</button>
)}
</li>
);
}
atom을 수정하게 만들자
const setToDos = useSetRecoilState(toDoState);
수정하고자 하는 to do의 경로를 찾자
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name }
} = event;
setToDos((oldToDos) => {
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
console.log(targetIndex);
return oldToDos;
});
};
새로운 to do를 만들어서 원래 to do를 업데이트하자
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name }
} = event;
setToDos((oldToDos) => {
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
const oldToDo = oldToDos[targetIndex];
const newToDo = { text, id, category: name };
console.log('replace the to do in the index', targetIndex, 'with', newToDo);
return oldToDos;
});
};
배열의 원소를 어떻게 교체하는지에 대해 배우자
⇒ slice
를 사용하자
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name }
} = event;
setToDos((oldToDos) => {
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
const oldToDo = oldToDos[targetIndex];
const newToDo = { text, id, category: name as any };
console.log('replace the to do in the index', targetIndex, 'with', newToDo);
return [...oldToDos.slice(0, targetIndex), newToDo, ...oldToDos.slice(targetIndex + 1)];
});
};
💡 **as any** ? 타입스크립트에게 확인할 필요 없다고 알려줌
Recoil의 selector 개념에 대해 공부하자
Selector : atom의 output을 변형시키는 도구
atom.tsx
import { atom, selector } from 'recoil';
export const toDoSelector = selector({
key: 'toDoSelector',
get: ({ get }) => {
const toDos = get(toDoState);
return toDos.length;
}
});
selectorOutput을 얻어보자
const selectorOutput = useRecoilValue(toDoSelector);
필터링해 조건을 만족하는 to do만 얻어내자
export const toDoSelector = selector({
key: 'toDoSelector',
get: ({ get }) => {
const toDos = get(toDoState);
return [toDos.filter((toDo) => toDo.category === 'TO_DO'), toDos.filter((toDo) => toDo.category === 'DOING'), toDos.filter((toDo) => toDo.category === 'DONE')];
}
});
배열을 쪼개자
const [toDo, doing, done] = useRecoilValue(toDoSelector);
나눠서 보여주자
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
return (
<div>
<h1>To Dos</h1>
<hr />
<CreateToDo />
<h2>To Do</h2>
<ul>
{toDo.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</ul>
<hr />
<h2>Doing</h2>
<ul>
{doing.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</ul>
<hr />
<h2>Done</h2>
<ul>
{done.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</ul>
</div>
);
}
한번에 하나의 카테고리만 보여주자
state 추가하자
// atom.ts
export const categoryState = atom({
key: 'category',
default: 'TO_DO'
});
select 추가하자
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
return (
<div>
<h1>To Dos</h1>
<hr />
<select>
<option value='TO_DO'>To Do</option>
<option value='DOING'>Doing</option>
<option value='DONE'>Done</option>
</select>
<CreateToDo />
</div>
);
}
select의 이벤트를 감지하자
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
console.log(event.currentTarget.value);
};
value를 categoryState atom과 연결하자
function ToDoList() {
const [toDo, doing, done] = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
return (
<div>
<h1>To Dos</h1>
<hr />
<select value={category} onInput={onInput}>
<option value='TO_DO'>To Do</option>
<option value='DOING'>Doing</option>
<option value='DONE'>Done</option>
</select>
<CreateToDo />
</div>
);
}
카테고리를 분류해서 보여주자
{category === 'TO_DO' && toDo.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
{category === 'DOING' && doing.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
{category === 'DONE' && done.map((aToDo) => <ToDo key={aToDo.id} {...aToDo} />)}
// atom.tsx
export const toDoSelector = selector({
key: 'toDoSelector',
get: ({ get }) => {
const toDos = get(toDoState);
const category = get(categoryState);
return toDos.filter((toDo) => toDo.category === category);
}
});
function ToDoList() {
const toDos = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value);
};
return (
<div>
<h1>To Dos</h1>
<hr />
<select value={category} onInput={onInput}>
<option value='TO_DO'>To Do</option>
<option value='DOING'>Doing</option>
<option value='DONE'>Done</option>
</select>
<CreateToDo />
{toDos?.map((toDo) => (
<ToDo key={toDo.id} {...toDo} />
))}
</div>
);
}
toDo의 카테고리가 categoryState에 따라서 추가되게 해보자
CreateToDo.tsx 수정하자
const category = useRecoilValue(categoryState);
const handleValid = ({ toDo }: IForm) => {
setToDos((oldToDos) => [{ text: toDo, id: Date.now(), category: category }, ...oldToDos]);
setValue('toDo', '');
};
⛔ **에러발생 :** category는 string인데 toDo의 카테고리는 세 종류로 제한됐기 때문
// atom.tsx
type categories = 'TO_DO' | 'DOING' | 'DONE';
export interface IToDo {
text: string;
id: number;
category: categories;
}
// ToDoList.tsx
function ToDoList() {
const toDos = useRecoilValue(toDoSelector);
const [category, setCategory] = useRecoilState(categoryState);
const onInput = (event: React.FormEvent<HTMLSelectElement>) => {
setCategory(event.currentTarget.value as any);
};
// ToDo.tsx
function ToDo({ text, category, id }: IToDo) {
const setToDos = useSetRecoilState(toDoState);
const onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const {
currentTarget: { name }
} = event;
setToDos((oldToDos) => {
const targetIndex = oldToDos.findIndex((toDo) => toDo.id === id);
const oldToDo = oldToDos[targetIndex];
const newToDo = { text, id, category: name as any };
console.log('replace the to do in the index', targetIndex, 'with', newToDo);
return [...oldToDos.slice(0, targetIndex), newToDo, ...oldToDos.slice(targetIndex + 1)];
});
enum 만들자
// atom.tsx
export enum Categories {
'TO_DO',
'DOING',
'DONE'
}
export interface IToDo {
text: string;
id: number;
category: Categories;
}
export const categoryState = atom<Categories>({
key: 'category',
default: Categories.TO_DO
});
enum을 atom 밖에서 사용해보자
// ToDoList.tsx
<select value={category} onInput={onInput}>
<option value={Categories.TO_DO}>To Do</option>
<option value={Categories.DOING}>Doing</option>
<option value={Categories.DONE}>Done</option>
</select>
return (
<li>
<span>{text}</span>
{category !== Categories.TO_DO && (
<button name={Categories.TO_DO} onClick={onClick}>
ToDo
</button>
)}
{category !== Categories.DOING && (
<button name={Categories.DOING} onClick={onClick}>
Doing
</button>
)}
{category !== Categories.DONE && (
<button name={Categories.DONE} onClick={onClick}>
Done
</button>
)}
</li>
);
}
⛔ **오류발생** : 사실 enum의 값들은 숫자로 저장되어있다.
⇒ Categories.TO_DO 값을 문자열로 바꿔주자
// atom.tsx
export enum Categories {
'TO_DO' = 'TO_DO',
'DOING' = 'DOING',
'DONE' = 'DONE'
}
💪 Challenge