728x90

App.js
import { useState, useRef, useEffect } from "react"; import "./App.css"; import DiaryEditor from "./DiaryEditor"; import DiaryList from "./DiaryList"; // https://jsonplaceholder.typicode.com/comments function App() { const [data, setData] = useState([]); const dataId = useRef(0); const getData = async () => { const res = await fetch( "https://jsonplaceholder.typicode.com/comments" ).then((res) => res.json()); console.log(res); const initData = res.slice(0, 20).map((it) => { return { author: it.email, content: it.body, emotion: Math.floor(Math.random() * 5) + 1, created_date: new Date().getTime(), id: dataId.current++, }; }); setData(initData); }; console.log(data); useEffect(() => { getData(); }, []); const onCreate = (author, content, emotion) => { const created_date = new Date().getTime(); const newItem = { author, content, emotion, created_date, id: dataId.current, }; dataId.current += 1; setData([newItem, ...data]); }; const onRemove = (targetId) => { console.log(`${targetId}가 삭제되었습니다.`); const newDiaryList = data.filter((it) => it.id !== targetId); setData(newDiaryList); }; const onEdit = (targetId, newContent) => { setData( data.map((it) => it.id === targetId ? { ...it, content: newContent } : it ) ); }; return ( <div className="App"> <DiaryEditor onCreate={onCreate} /> <DiaryList onRemove={onRemove} diaryList={data} onEdit={onEdit} /> </div> ); } export default App;
App.css
/* App */ .App { } /* editor */ .DiaryEditor { border: 1px solid gray; text-align: center; padding: 20px; } .DiaryEditor input, textarea { margin-bottom: 20px; width: 500px; } .DiaryEditor input { padding: 10px; } .DiaryEditor textarea { padding: 10px; height: 150px; } .DiaryEditor select { width: 300px; padding: 10px; margin-bottom: 20px; } .DiaryEditor button { width: 500px; padding: 10px; cursor: pointer; } /* List */ .DiaryList { border: 1px solid gray; padding: 20px; margin-top: 20px; } .DiaryList h2 { text-align: center; } /* List Item */ .DiaryItem { background-color: rgb(240, 240, 240); margin-bottom: 10px; padding: 20px; } .DiaryItem span { margin-right: 10px; } .DiaryItem .info { border-bottom: 1px solid gray; padding-bottom: 10px; margin-bottom: 10px; } .DiaryItem .date { color: gray; } .DiaryItem .content { margin-bottom: 30px; margin-top: 30px; font-weight: bold; } .DiaryItem textarea { padding: 10px; }
DiaryEditor.js
import { useRef, useState } from "react"; const DiaryEditor = ({ onCreate }) => { const authorInput = useRef(); const contentInput = useRef(); const [state, setState] = useState({ author: "", content: "", emotion: 1, }); const handleChangeState = (e) => { setState({ ...state, [e.target.name]: e.target.value, }); }; const handleSubmit = () => { if (state.author.length < 1) { alert("작성자는 최소 1글자 이상이어야 합니다."); authorInput.current.focus(); return; } if (state.content.length < 5) { alert("본문 내용은 최소 5글자 이상이어야 합니다."); contentInput.current.focus(); return; } onCreate(state.author, state.content, state.emotion); alert("저장 성공"); setState({ author: "", content: "", emotion: 1, }); }; return ( <div className="DiaryEditor"> <h2>오늘의 일기</h2> <div> <input ref={authorInput} name="author" value={state.author} onChange={handleChangeState} /> </div> <div> <textarea ref={contentInput} name="content" value={state.content} onChange={handleChangeState} /> </div> <div> <span>오늘의 감정점수 : </span> <select name="emotion" value={state.emotion} onChange={handleChangeState} > <option value={1}>1</option> <option value={2}>2</option> <option value={3}>3</option> <option value={4}>4</option> <option value={5}>5</option> </select> </div> <div> <button onClick={handleSubmit}>일기 저장하기</button> </div> </div> ); }; export default DiaryEditor;
DiaryItem.js
import { useState, useRef } from "react"; const DiaryItem = ({ author, content, emotion, created_date, id, onRemove, onEdit, }) => { const [isEdit, setIsEdit] = useState(false); const toggleisEdit = () => setIsEdit(!isEdit); const [localContent, setLocalContent] = useState(content); const localContentInput = useRef(); const handleRemove = () => { if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) { onRemove(id); } }; const handleQuitEdit = () => { setIsEdit(false); setLocalContent(content); }; const handleEdit = () => { if (localContent.length < 5) { localContentInput.current.focus(); return; } if (window.confirm(`${id}번째 일기를 수정하시겠습니까?`)) { onEdit(id, localContent); toggleisEdit(); } }; return ( <div className="DiaryItem"> <div className="info"> <span> 작성자 : {author} | 감정점수 : {emotion} </span> <br /> <span className="date">{new Date(created_date).toLocaleString()}</span> </div> <div className="content"> {isEdit ? ( <> <textarea ref={localContentInput} value={localContent} onChange={(e) => setLocalContent(e.target.value)} /> </> ) : ( <>{content}</> )} </div> {isEdit ? ( <> <button onClick={handleQuitEdit}>수정 취소</button> <button onClick={handleEdit}>수정 완료</button> </> ) : ( <> <button onClick={handleRemove}>삭제하기</button> <button onClick={toggleisEdit}>수정하기</button> </> )} </div> ); }; export default DiaryItem;
DiaryList.js
import DiaryItem from "./DiaryItem"; const DiaryList = ({ diaryList, onRemove, onEdit }) => { return ( <div className="DiaryList"> <div>{diaryList.length}개의 일기가 있습니다.</div> <div> {diaryList.map((it) => ( <DiaryItem key={it.id} {...it} onRemove={onRemove} onEdit={onEdit} /> ))} </div> </div> ); }; DiaryList.defaultProps = { diaryList: [], }; export default DiaryList;
한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 - 인프런 | 강의
개념부터 독특한 프로젝트까지 함께 다뤄보며 자바스크립트와 리액트를 이 강의로 한 번에 끝내요. 학습은 짧게, 응용은 길게 17시간 분량의 All-in-one 강의!, 리액트, 한 강의로 끝장낼 수 있어요.
www.inflearn.com
728x90
'🤯TIL > React' 카테고리의 다른 글
[React] async와 await를 사용한 CRUD (0) | 2023.11.30 |
---|---|
[한입 크기로 잘라 먹는 리액트] React에서 API 호출하기 (0) | 2023.11.19 |
[한입 크기로 잘라 먹는 리액트] React Lifecycle 제어하기 - useEffect (1) | 2023.11.19 |
[한입 크기로 잘라 먹는 리액트] React에서 배열 사용하기 4 - 데이터 수정하기 (1) | 2023.11.19 |
[한입 크기로 잘라 먹는 리액트] React에서 배열 사용하기 3 - 데이터 삭제하기 (0) | 2023.11.19 |