2022. 2. 8. 15:44ใWEB Dev/ToyProject
๐ท CloneTodo โ - Todomate Clone Project | Team CloneMate
CloneTodo : ๋ง์ ์ฌ๋๋ค์ด ์ฌ๋ํ๋ ํฌ๋๋ฉ์ดํธ๋ฅผ ํด๋ก ํ์ฌ ์น ์๋น์ค๋ฅผ ๋ฐฐํฌํด๋ณด๋ ํ๋ก์ ํธ
ํ ์ผ(to-do) ๋ง๋ค๊ธฐ๋ฅผ ์งํํ๊ณ ์๋ค.
์ ๋ฒ์ฃผ ํ๋ฐ๋ถํฐ ์ง์คํ๊ณ ์๋๋ฐ ๋ชฉํ์ ๋ง๊ฒ ํ ์ผ ๊ฐ์ ๋ฟ๋ ค ์ฃผ๋ ๊ฒ์ด ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค.
์ฌ๋ฌ๋ชจ๋ก ์ค์ฌ์ด ๋๋ ๊ธฐ๋ฅ์ด๋ค๋ณด๋ state ๋ค๋ฃจ๋ ๊ฒ๋ ๋ญ๊ฐ ๋ ๊ณ ๋ํ ๋ ๊ธฐ๋ถ์ด๋ค.
1. Feed์ ๋ชฉํ ๋ฟ๋ฆฌ๊ธฐ
atom์ผ๋ก ์ค์ ํด ๋ goal ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ Main - Feed ์์๋ ์ธ ์ ์๋๋ก ํ๋ค.
export const goalsData = atom({ // ๋ก๋ฉ ์ ๋ชจ๋ ๋ชฉํ ๋ฟ๋ฆฌ๊ธฐ
key: "goalsData",
default: [
{
"goal_id": 0,
"next_goal_id": 1,
"title": "์ฒซ ๋ฒ์งธ ๋ชฉํ",
"privacy": "PUB",
"box_color": "",
"title_color": "#3CB371"
},
{
"goal_id": 1,
"next_goal_id": 2,
"title": "๋ ๋ฒ์งธ ๋ชฉํ",
"privacy": "PRI",
"box_color": "",
"title_color": "#C71585"
}
]
})
import { useRecoilState, useRecoilValue } from "recoil";
import { goalsData } from "../atoms/todoData";
export default function Feed() {
/* Hook ์ ์ธ ์์ */
/* atom ์์ */
let goal = useRecoilValue(goalsData);
return ();
}
useRecoilState๋ฅผ ์ฐ์ง ์๊ณ useRecoilValue๋ฅผ ์ด ์ด์ ๋ ๋ชฉํ์ ์์ฑ, ์์ , ์ญ์ ๋ ๋ณ๋์ ์ปดํฌ๋ํธ์์ ์งํํ๊ธฐ ๋๋ฌธ์ด๋ค.
useRecoilValue๋ก ๊ฐ๋ง ๋ฐ์์ ๋ฟ๋ ค์ค ๋ค์ ํ์ด์ง์ ๋งํฌ์ ๋ ์ ์๋๋ก goal.map์ผ๋ก ํ๋ฉด์ ๋ฟ๋ ค์ค๋ค.
๊ทธ๋ฆฌ๊ณ ํด๋ฆญํ๋ฉด ๊ธฐ๋ฅ๋ค์ด ๋์ํด์ผ ํ๊ธฐ ๋๋ฌธ์ <Button> UI ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ์ค๋ค.
return (
<Box className='feed-box'>
<h2 className="feed-title">Feed</h2>
<Box className="feed-goals-list-box">
<List className="goals-list-wrap" >
{
goal.map((goal, index) => {
return ( <ListItem className="goals-listItem" id={goal.goal_id} key={goal.goal_id} >
<Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickTodoHandler(e))}} data-index={index} >
<LibraryBooksIcon className="goals-listItem-icon" />
<ListItemText className="goals-listItem-text" id={goal.goal_id} name={goal.goal_id} sx={{ color:goal.title_color }} >{goal.title}</ListItemText>
<ListItemText className="goals-listItem-add-icon" ><span>+</span></ListItemText>
</Button>
)
}
)
}
</List>
</Box>
</Box>
</>
)
2. ๊ฐ๋ณ ๋ชฉํ ํด๋ฆญํ์ ๋ ํ ์ผ ์ ๋ ฅ
ํ๋ฉด์ ๋ฟ๋ ค์ง ๋ชฉํ๋ฅผ ํด๋ฆญํ์ ๋ ํด๋น ํด๋ฆญ ๋ถ๋ถ ํ๋จ์ textfield๊ฐ ๋ํ๋๊ณ
ํ ์ผ์ ์ ๋ ฅํ์ ๋ ์๋ก์ด todo state๊ฐ ๋ง๋ค์ด ์ง ์ ์๋๋ก ํ๋ค.
์ฌ๊ธฐ์๋ InputBase๋ฅผ ์ฌ์ฉํ ๊ฑด๋ฐ ๋ชฉํ ๋ง๋ค ๋์ฒ๋ผ ๊ฒฝํํ๋ ๊ฒ์ฒ๋ผ readonly์์ฑ์ ๋ฐ๋ผ true์ผ ๋๋ ์์ ์ด ์๋๊ณ false์ผ ๋๋ ์์ ์ด ๊ฐ๋ฅํ๋๋กํ๊ธฐ ์ํด์์ด๋ค.
๋ชฉํ๋ฅผ ํด๋ฆญํ๋ฉด CreateInput์ด๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ์์ฑ๋๊ณ ํด๋น ์ปดํฌ๋ํธ๊ฐ ๋ชฉํ ์๋ ๋ถ์ ์ ์๋๋ก ํ๊ธฐ ์ํด ๋ชฉํ์ onClick ์ด๋ฒคํธ๋ฅผ ์์ฑํ๊ณ CreateInput ์ปดํฌ๋ํธ๋ฅผ ํ๋จ์ ๋ณ๋๋ก ๋ง๋ค์๋ค.
์ด CreateInput ์ปดํฌ๋ํธ์์ ํ ์ผ์ ์ ๋ ฅํ๊ณ ์ํฐ๋ฅผ ์น๋ฉด (submit ๋ฒํผ ๋ ธ์ถํ์ง ์์)
์๋ createTodoState์ ๋ด๊ธฐ๊ณ react form hook์ ํตํด ์ ์ถ๋๊ฒ ๋๋ค.
// input ์์ฑ
export function CreateInput(props) {
let id = props.id;
let defaultDate = new Date().toJSON().substring(0,10);
const isGoalSelected = props.isGoalSelected;
const setIsGoalSelected = props.setIsGoalSelected;
console.log("isGoalSelected" , isGoalSelected)
/* Hook ์ ์ธ ์์ */
/* atom ์์ */
const { register, handleSubmit, errors, watch } = useForm({ mode: "onChange" });
let [todo, setTodo] = useRecoilState(todoData);
let [createTodoState, setCreactTodoState] = useState({
"todo_id": "",
"goal_id": "", //๋ฌถ์ฌ์๋ goal id
"next_todo_id": "", //๋ค์ todo id (์์์ง์ ์ฉ)
"title": "",
"date": "",
"end_repeat_date": "", //๋ฐ๋ณต ์ข
๋ฃ ์ผ์. ๋ฐ๋ณต ์์ผ๋ฉด date ์ ๊ฐ์ด ๊ฐ๊ฑฐ๋ ์์
"repeat_days": {
"sun": "N", //y ๋ฉด ์ผ์์ผ ๋ฐ๋ณต, n ์ด๋ฉด ๋ฐ๋ณต x
"mon": "N",
"tue": "N",
"wed": "N",
"thu": "N",
"fri": "N",
"sat": "N",
},
"check_yn" : "N" //๋ฌ์ฑ์ฌ๋ถ
});
/* Hook ์ ์ธ ๋ */
/* ํจ์ ์ ์ธ ์์ */
//input ํจ์
const onInputChange = (e) => {
setCreactTodoState({...createTodoState, title: e.target.value})
// console.log(createTodoState)
}
//todo ์ถ๊ฐ ํจ์
const addTodo = (data, id) => {
const copy_todo_state = [...todo];
//์ถ๊ฐ๋๋ state 'todo_id', 'goal_id', 'next_todo_id', 'date', 'end_repeat_date'
// 'repeat_days'์ 'check_yn'์ default ๊ฐ "N" ,
data.todo_id = copy_todo_state.length;
data.goal_id = parseInt(id); //key๋ฅผ ์ํ id ์ถ๊ฐ
data.next_todo_id = copy_todo_state.length+1;
data.date = defaultDate;
data.end_repeat_date = defaultDate;
copy_todo_state.push(data);
setTodo(copy_todo_state, console.log(copy_todo_state))
}
// console.log(goal)//react-form-hook submit ํจ์
const onSubmit = (data) => {
console.log(data.goal_id)
const index = data.goal_id
const newArr = [...isGoalSelected];
setIsGoalSelected(newArr[index] = false)
setCreactTodoState(JSON.stringify(createTodoState));
addTodo(createTodoState, index);
}
const onError = (error) => {
console.log(error);
};
/* ํจ์ ์ ์ธ ๋ */
return (
<Box className="goals-todo-input-create-Box">
<form onSubmit={handleSubmit(onSubmit,onError)}>
<div className="goals-todo-input-create-wrap">
<CheckBoxOutlineBlankIcon className="goals-todo-input-create-check-icon"/>
<InputBase {...register("title")} id="todo-input" className="goals-todo-input-create-field" placeholder="ํ ์ผ์ ์
๋ ฅํด์ฃผ์ธ์." onChange={onInputChange} />
<InputBase {...register("goal_id")} id="todo-goal-id-input" className="todo-goal-create-id" type="hidden" value={id} />
<Button type="submit" className="goals-todo-input-btn"><MoreHorizIcon className="goals-todo-list-input-btn-icon" /></Button>
</div>
</form>
</Box>
)
}
์ฌ๊ธฐ์ ๋งต์ผ๋ก ๋ฟ๋ ค์ง๋ ํ๊ทธ๋ฅผ ๊ฐ๋ณ ์ ํํ๋ ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ ๊ตฌํํ๋๋ฐ ๊ต์ฅํ ์ ๋ฅผ ๋จน์๋ค.
๊ทธ๋ฌ๋ค ์๋ ๋ธ๋ก๊ทธ์ ๋์์ ๊ต์ฅํ ๋ง์ด ๋ฐ์๋ค.
1. ๋ชฉํ ๋ฐฐ์ด๋งํผ boolean ๊ฐ์ผ๋ก ์ฑ์์ง ๋ฐฐ์ด ์์ฑํ๊ธฐ
let [isGoalSelected, setIsGoalSelected] = useState(Array(goal.length).fill(false) );
๋ก ๋ณ๋์ ๋ฐฐ์ด์ ํ๋ ์์ฑํด์ค๋ค.
2. ๊ฐ๋ณ ๋ชฉํ ๋ฒํผ์ ํ ๋นํ ์ด๋ฒคํธ ์์ฑ
<Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickCreateTodoHandler(e))}} data-index={index} ></Button>
//๋ชฉํ ํด๋ฆญ ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ
const clickCreateTodoHandler = (e) => {
const index = e.currentTarget.dataset.index;
const newArr = Array(goal.length).fill(false) ;
newArr[index] = true;
setIsGoalSelected(newArr)
}
์์ ๊ฐ์ด ๊ฐ๋ณ ๋ชฉํ ๋ฒํผ์ onClick ์ด๋ฒคํธ๋ฅผ ๋ฃ์ด์ค ๋ค์ ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ๊ตฌํํ๋ค.
๊ฐ๋ณ Button์ dataset์ผ๋ก ์ค์ ๋ index ๊ฐ์ ๋ฐ์์ค๊ณ (goal ๋ฐฐ์ด์ ์ธ๋ฑ์ค์ ๊ฐ์)
newArr๋ก ๊ธฐ์กด ๋ฐฐ์ด๊ณผ ๊ฐ์ ๋ฐฐ์ด์ ์์ฑํ ๋ค์ ํด๋น ์์์ false ๊ฐ์ true๋ก ๋ฐ๊ฟ์ค๋ค์ isGoalSelected ๊ฐ์ ๋ณ๊ฒฝํด์ค๋ค. (์ด ์ปดํฌ๋ํธ์์๋ง ์ธ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ recoil์ ๋ฃ์ง ์๊ณ useState๋ก ์ฌ์ฉํ๋ค.
3. ์กฐ๊ฑด๋ฌธ์ผ๋ก CreateInput ์ปดํฌ๋ํธ ์์ฑ
ํด๋น ์ธ๋ฑ์ค์ isGoalSelected๊ฐ true ์ธ ๊ฒฝ์ฐ CreateInput ์ปดํฌ๋ํธ๊ฐ ๋ํ๋ ์ ์๋๋ก ์กฐ๊ฑด๋ฌธ์ ์ค์ ํ๋ค.
{isGoalSelected[index] ? <CreateInput />: null}
์ด๋ ๊ฒ ํ๋ฉด ํด๋น ์ธ๋ฑ์ค ๋ถ๋ถ์ isGoalSelected์ ๊ฐ์ ๋ฐ๋ผ CreateInput ์ด ๋ฑ์ฅํ๊ฒ ๋๋ค.
3. todo state ๊ฐ ๋ชฉํ์ ๋ง๊ฒ ๋ฟ๋ฆฌ๊ธฐ
์ด์ ์์ฑ๋ ํ ์ผ ๋ค์ ๋ชฉํ ์๋์ ๋ ธ์ถ๋๋๋ก ํด์ค๋ค.
๋ชฉํ์ id์ ํ ์ผ์ด ๊ฐ์ง๊ณ ์๋ goal_id๊ฐ ๋งค์นญ์ด ๋์ด์ผ ํ๊ณ ํด๋น ๋ชฉํ ์๋์ ํ ์ผ์ด ๋ฟ๋ ค์ ธ์ผ ํ๋ค.
๋ชฉํ์ ๋์ผํ๊ฒ recoil๋ก ๋๋ฏธ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ atom์ผ๋ก ๊ฐ์ง๊ณ ์จ๋ค.
import { todoData } from "../atoms/todoData";
export default function Feed() {
/* Hook ์ ์ธ ์์ */
/* atom ์์ */
let todo = useRecoilValue(todoData);
return (<>
<Box className='feed-box'>
<h2 className="feed-title">Feed</h2>
<Box className="feed-goals-list-box">
<List className="goals-list-wrap" >
{
goal.map((goal, index) => {
return ( <ListItem className="goals-listItem" id={goal.goal_id} key={goal.goal_id} >
<Button className="goals-listItem-text-wrap" isselected={isGoalSelected[index]} id={goal.goal_id} onClick={(e)=>{(clickCreateTodoHandler(e))}} data-index={index} >
<LibraryBooksIcon className="goals-listItem-icon" />
<ListItemText className="goals-listItem-text" id={goal.goal_id} name={goal.goal_id} sx={{ color:goal.title_color }} >{goal.title}</ListItemText>
<ListItemText className="goals-listItem-add-icon" ><span>+</span></ListItemText>
</Button>
{todo.map((todo,index)=>{
return (
<Box className="goals-todo-input-list-Box" key={index} onClick={clickTodoModalHandler} data-key={index}>
{goal.goal_id === parseInt(todo.goal_id) ? (<>
<div className="goals-todo-input-list-check-wrap">
<CheckBoxOutlineBlankIcon className="goals-todo-list-input-check-icon"/>
<InputBase key={`todo${index}`} inputProps={{readOnly: todoReadOnly[index].toString(),}} type="text" name={todo.title} id="todo-input" value={todo.title} onChange={todoEditEventHandler} className="goals-todo-list-input"></InputBase>
{
}
</div>
<Button className="goals-todo-list-input-btn" ><MoreHorizIcon className="goals-todo-list-input-btn-icon" /></Button>
</>
) : null}
</Box>
)
})}
{isGoalSelected[index] ? <CreateInput id={goal.goal_id} isGoalSelected={isGoalSelected} setIsGoalSelected={setIsGoalSelected} />: null}
</ListItem>
)
})
}
</List>
</Box>
</Box>
{/* ๋ชจ๋ฌ ์์ฑ */}
<TodoModal modalOpen={modalOpen} handleTodoModalClose={handleTodoModalClose}selectedTodo={selectedTodo} clickTodoEditHandler={clickTodoEditHandler} clickTodoDeleteHandler={clickTodoDeleteHandler} selectedInputIndex={selectedInputIndex} />
</>
);
}
๊ทธ๋ฆฌ๊ณ goal.map ์์ todo๋ฅผ ๋ฟ๋ ค์ฃผ๊ฒ ๋๋๋ฐ goal์ goal_id์ todo ์ goal_id๊ฐ ๊ฐ์ ๊ฒฝ์ฐ๋ง ์ถ๋ ค์ ธ map์ผ๋ก ๋ฟ๋ ค์ง ์ ์๊ฒ ํ์๋ค.
์ด์ ๊ฐ ํ ์ผ์ ํด๋ฆญํ๋ฉด ๋ชจ๋ฌ์ด ์์ฑ๋๊ณ ํด๋น ๋ชจ๋ฌ์์ 5๊ฐ์ง ๊ธฐ๋ฅ (์์ , ์ญ์ , ๋ด์ผ ํ๊ธฐ, ๋ ์ง ๋ฐ๊พธ๊ธฐ, ์์๋ณ๊ฒฝ) ๋ค์ฏ ๊ฐ์ ๊ธฐ๋ฅ์ด ๋์ํ๋๋ก ํด์ผ ํ๋ค.
๊ทธ๋ฐ๋ฐ ์ด๊ฒ ์์ฑ๋ณด๋ค ๋ ์ด๋ ค์ด ๊ฑฐ ๊ฐ์....
'WEB Dev > ToyProject' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 38 (0) | 2022.03.21 |
---|---|
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 36 (0) | 2022.02.08 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 34 (0) | 2022.02.03 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 33 (0) | 2022.02.01 |
[ToyProject-Todomate] ํฌ๋๋ฉ์ดํธ ํด๋ก ํ๋ก์ ํธ 32 (0) | 2022.01.26 |