코딩앙마님의 강의를 보고 작성한 글입니다.
https://www.youtube.com/watch?v=ymmiuSl1t5k&list=PLZKTXPmaJk8J_fHAzPLH8CJ_HO_M33e7-&index=16
PUT
현재 단어를 완료했다는 의미로 체크를 하면 회색으로 표시되도록 하였는데 아직 새로고침을 하거나 나갔다 들어오면 초기값으로 변경되어 있다. 그래서 저장되어 있는 데이터의 isDone 상태를 바꾸기 위해 PUT 메소드를 사용할 것이다.
Word 컴포넌트의 toggleDone 함수를 수정해보자.
//Word.js
function toggleDone(){
fetch(`http://localhost:3001/words/${word.id}`, {
method : 'PUT',
headers : {
'content-Type' : 'application/json',
},
body : JSON.stringify({
...word,
isDone: !isDone
})
})
.then(res => {
if(res.ok) {
setIsDone(!isDone);
}
})
}
fetch의 두 번째 인자로 객체를 주어 이 객체 안에는 요청의 옵션들을 넣어준다.
content-Type 이란 보내는 리소스의 타입을 의미한다. 문자열, 이미지, html, json 다양한 형태가 올 수 있다.
body는 수정을 위한 정보들을 입력한다.
기존의 word에서 (... word) isDone 값을 변경해준다. 그리고 이 값은 JSON으로 변환하기 위해 JSON.stringify로 감싸준다.
이렇게 받아온 값이 ok이면 isDone 값을 변경해준다.
이제 새로고침을 해도! 변경된 값이 잘 유지되는 것을 볼 수 있다.
DELETE
이제 단어를 삭제해보자.
삭제 버튼을 누르면 정말 단어를 삭제할 것인지 물어본 다음 삭제할 수 있도록 한다.
//Word.js
function del(){
if(window.confirm('삭제하시겠습니까?')){
fetch(`http://localhost:3001/words/${word.id}`,{
method: 'DELETE'
})
}
}
그리고 삭제 버튼에 onClick으로 del을 연결해준다.
구조는 PUT과 같은데 DELETE는 따로 수정해야 할 정보가 없기 때문에 method만 주면 된다.
하지만!
삭제 버튼을 눌러보면 confirm이 뜨고 확인을 눌러도 변화가 일어나지 않는다. 새로고침을 해야 단어가 삭제된다. 이것은 단어가 삭제된 후에 단어 리스트를 다시 그려주지 않았기 때문이다.
따라서 확인 버튼을 누르면 단어 리스트를 다시 랜더링 하도록 해야 한다.
먼저 word를 state로 만들어준다.
export default function Word({word: w}){
const [word, setWord] = useState(w);
...
word를 state로 만들면서 이름이 겹치지 않기 위해 {word: w}로 변경해주었다. 이것은 props로 넘어온 word를 w라는 변수명에 할당한다는 뜻이다.
function del(){
if(window.confirm('삭제하시겠습니까?')){
fetch(`http://localhost:3001/words/${word.id}`,{
method: 'DELETE'
}).then(res => {
if(res.ok){
setWord({id:0});
}
})
}
}
if(word.id === 0) {
return null;
}
불러오는 것에 성공하면 setWord로 id를 0으로 만들어준다.
그리고 word.id가 0일 때 null을 리턴하게 하면 id가 0인 기존 데이터를 날려서 null로 하겠다는 뜻이다.
이러면 삭제 버튼을 누른 후 리스트가 랜더링 되면서 정상적으로 사라지게 된다.
단어 추가
단어를 추가하기 위해 CreateWord라는 컴포넌트를 새로 만든다.
//CreateWord.js
export default function CreateWord(){
return <form>
<div className="input_area">
<label>Eng</label>
<input type="text" placeholder="예) computer" />
</div>
<div className="input_area">
<label>Kor</label>
<input type="text" placeholder="예) 컴퓨터" />
</div>
<div className="input_area">
<label>Day</label>
<select>
<option>1</option>
<option>2</option>
</select>
</div>
<button>저장</button>
</form>
}
그리고 App.js 에 CreateWord를 추가하고 Header에서 a태그를 Link태그로 변경하고 링크를 추가한다.
//App.js
<Route path="/create_word" element={<CreateWord/>}/>
//Header.js
<Link to='/create_word' className="link">
단어 추가
</Link>
현재 CreateWord에서 Day 부분을 하드 코딩해주었기 때문에 만들어준 훅을 이용해서 Day를 불러와준다.
import useFetch from "../hooks/useFetch"
export default function CreateWord(){
const days = useFetch("http://localhost:3001/days");
return <form>
<div className="input_area">
<label>Eng</label>
<input type="text" placeholder="예) computer" />
</div>
<div className="input_area">
<label>Kor</label>
<input type="text" placeholder="예) 컴퓨터" />
</div>
<div className="input_area">
<label>Day</label>
<select>
{days.map(day => (
<option key={day.id} value={day.day}>
{day.day}
</option>
))}
</select>
</div>
<button>저장</button>
</form>
}
function onSubmit(e){
e.preventDefault();
}
const engRef = useRef(null);
저장 버튼을 눌렀을 때 새로고침 되는 것을 방지하기 위한 onSubmit 함수를 만들어서 form에 연결한다.
useRef는 DOM을 조작할 수 있게 하는 훅인데 예를 들어 스크롤 위치를 확인하거나 포커스를 줄 때 사용할 수 있다. null은 초기값이다.
<div className="input_area">
<label>Eng</label>
<input type="text" placeholder="예) computer" ref={engRef} />
</div>
이런 식으로 각 ref에 해당하는 것에 연결해주고 이제 word에서 PUT을 했던 것처럼 POST를 해주면 된다.
function onSubmit(e){
e.preventDefault();
fetch(`http://localhost:3001/words/`, {
method : 'POST',
headers : {
'content-Type' : 'application/json',
},
body : JSON.stringify({
day : dayRef.current.value,
eng : engRef.current.value,
kor : korRef.current.value,
isDone : false
})
})
.then(res => {
if(res.ok) {
alert('생성이 완료 되었습니다.')
}
})
}
주소는 words 까지만 작성을 하고 method는 POST,
body로 전달할 정보는 day, eng, kor, isDone으로 앞의 3개는 입력된 값을 주면 되고 isDone은 초기값 false로 고정하면 된다. 그리고 POST가 완료되면 alert 창을 띄운다.
생성할 때마다 따로 그 페이지를 확인하는 것이 아니라 생성이 완료되면 해당 페이지를 바로 보여주도록 해보자.
useNavigate를 이용해서 구현할 수 있다.
//CreateWord.js
const history = useNavigate();
function onSubmit(e){
e.preventDefault();
fetch(`http://localhost:3001/words/`, {
method : 'POST',
headers : {
'content-Type' : 'application/json',
},
body : JSON.stringify({
day : dayRef.current.value,
eng : engRef.current.value,
kor : korRef.current.value,
isDone : false
})
})
.then(res => {
if(res.ok) {
alert('생성이 완료 되었습니다.');
history(`/day/${dayRef.current.value}`);
}
})
}
alert 창이 뜬 후에 창을 없애면 지정해둔 페이지로 이동하게 된다.
날짜 추가하기
단어를 추가하는 것과 유사하다.
import useFetch from "../hooks/useFetch"
import { useNavigate } from "react-router-dom";
export default function CreateDay(){
const days = useFetch("http://localhost:3001/days");
const history = useNavigate();
function addDay(){
fetch(`http://localhost:3001/days/`, {
method : 'POST',
headers : {
'content-Type' : 'application/json',
},
body : JSON.stringify({
day : days.length + 1
})
})
.then(res => {
if(res.ok) {
alert('생성이 완료 되었습니다.');
history(`/`);
}
});
}
return (
<div>
<h3>현재 일수: {days.length}일</h3>
<button onClick={addDay}>Day 추가</button>
</div>
);
}
현재 일수가 3일 때 day를 추가하면 4, 4일 땐 5... 이렇게 하나씩 늘어나기 때문에 days.length + 1을 해준다.
그리고 생성이 완료되었다는 팝업창이 뜨고 첫 화면으로 돌아가도록 한다.
마지막으로! 인터넷이 느린 경우 로딩 중이란 것을 알리기 위해 로딩 표시를 추가해보자.
메인화면에서 DayList가 로딩될 때, 단어 리스트가 로딩될 때 로딩 표시를 만들어보자.
//DayList.js
export default function DayList(){
const days = useFetch('http://localhost:3001/days');
if(days.length === 0){
return <span>Loading...</span>
}
...
}
//Day.js
return(
<>
<h2>Day {day}</h2>
{words.length === 0 && <span>Loading...</span>}
...
)
각각 days의 길이가 0일 때, words의 길이가 0일 때 Loading...이라는 글자가 표시되도록 하였다.
또 단어를 추가하는 경우 저장 버튼을 연속해서 누르다 보면 딜레이가 되면서 원하지 않은 결과를 얻게 될 수 있다. (빈 단어가 계속 추가되는 등)그래서 버튼을 누르고 통신 중일 때는 버튼을 다시 누를 수 없도록 해보자.
isLoading이라는 state를 만들어서 이 값이 false일 때만 onSubmit 함수가 실행되도록 할 것이다.
//CreateWord.js
const [isLoading, setIsLoading] = useState(false);
function onSubmit(e){
e.preventDefault();
if(!isLoading){
setIsLoading(true);
fetch(`http://localhost:3001/words/`, {
method : 'POST',
headers : {
'content-Type' : 'application/json',
},
body : JSON.stringify({
day : dayRef.current.value,
eng : engRef.current.value,
kor : korRef.current.value,
isDone : false
})
})
.then(res => {
if(res.ok) {
alert('생성이 완료 되었습니다.');
history(`/day/${dayRef.current.value}`);
setIsLoading(false);
}
})
}
}
<button style={{
opacity: isLoading ? 0.3 : 1,
}}>{isLoading ? "Saving..." : "저장"}</button>
isLoading이 false일 때 POST를 실행하는데 이때 isLoading을 true로 바꿔준다. 그리고 모든 과정이 완료되면 다시 false로 바꾼다.
버튼 역시 isLoading이 true일 땐 Saving...이라는 글자가 뜨면서 투명하게 바뀌고 isLoading이 false가 되면 다시 저장으로 뜨도록 한다.
이렇게 간단한 영어 단어장 사이트가 완성되었다!
이제 Day 이전 다음 버튼, Day 삭제 버튼 등을 자율적으로 만들어볼 예정이다.
'개발 공부 > React' 카테고리의 다른 글
리액트 429 에러 해결(useEffect 무한루프) (1) | 2022.10.05 |
---|---|
React - Fragment 사용하기 (0) | 2022.09.19 |
React #9 [json-server, REST API, custom hook] (0) | 2022.08.27 |
React #8 [json, 리액트 라우터] (0) | 2022.08.26 |
React #7 [컴포넌트, CSS, 이벤트, state, props] (0) | 2022.08.23 |