📍 목표: 아래 캡처 이미지와 같이 화면의 컴포넌트가 구성되어 있는 상황에서 상단 <Form /> 컴포넌트의 입력값 3개(작성자 이름(author), 제목(title), 본문(bodyText))를 서버에 보낸 후, 하단 <DiscussionsList /> 컴포넌트의 제일 위쪽에 form에서 보낸 새로운 데이터가 뜨게 하기
create-react-app으로 만든 폴더 구조는 다음과 같다.
|--- /fe-sprint-my-agora-states-react-client
| |--- /public
| |--- /img ## icon 등 assets 이미지 파일이 들어있는 폴더
| |--- index.html
| |--- /src
| |--- App.js
| |--- index.css
| |--- index.js
| |--- /components
| |--- Discussion.js
| |--- DiscussionsList.js
| |--- Form.js
| |--- package-lock.json
| |--- package.json
핵심은 상위 <App /> 컴포넌트에서 서버에 데이터를 전송할 수 있는 함수(addDiscussion)를 만들어서 <Form /> 컴포넌트에 props로 내려보낸 다음(상태 끌어올리기 - lifting state up), <Form /> 컴포넌트에서 <form> 태그에 onSubmit 이벤트 핸들러를 달아주고, 해당 이벤트 핸들러 안에서 서버에 데이터 전송을 요청하는 addDiscussion 함수를 호출하는 것이다.
<App /> 컴포넌트 코드 :
function App() {
const domain = "http://localhost:4000";
const [agoraStatesDiscussions, setAgoraStatesDiscussions] = useState([]);
const getAgoraStatesDiscussions = () => {
fetch(domain + "/discussions/")
.then((res) => res.json())
.then((data) => {
setAgoraStatesDiscussions(data);
});
};
useEffect(() => {
getAgoraStatesDiscussions();
}, []);
const addDiscussion = ({ title, author, bodyText }) => {
const newDiscussionData = {
title,
author,
bodyHTML: bodyText,
};
fetch(domain + "/discussions", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(newDiscussionData),
}).then((res) => {
if (res.status === 201) {
getAgoraStatesDiscussions();
}
});
};
return (
<main>
<h1 className="home">Home</h1>
<Form addDiscussion={addDiscussion} />
<DiscussionList
list={agoraStatesDiscussions}
/>
</main>
);
}
<Form /> 컴포넌트 코드:
function Form({ addDiscussion }) {
const handleNewDiscussion = (event) => {
// console.log(event.target); // <form> 태그 전체
// console.log(event.target[0].value); // 0번째 <input> 태그
event.preventDefault();
const author = event.target[0].value;
const title = event.target[1].value;
const bodyText = event.target[2].value;
addDiscussion({ author, title, bodyText });
};
return (
<section className="form__container">
<form
action=""
method="GET"
id="discussion__form"
onSubmit={handleNewDiscussion} // 여기가 핵심
>
<div className="form__input--wrapper">
<div className="form__input--name">
<label htmlFor="author">Name : </label>
<input
type="text"
name="author"
id="author"
placeholder="ex) kimcoding"
required
/>
</div>
<div className="form__input--title">
<label htmlFor="title">Title : </label>
<input
type="text"
name="title"
id="title"
placeholder="ex) array의 reduce 메서드 사용 시 accumulator 인자의 작동이 이해되지 않습니다."
required
/>
</div>
<div className="form__textbox">
<label htmlFor="story">Your question : </label>
<textarea
id="story"
name="story"
placeholder="What's your question?"
required
></textarea>
</div>
</div>
<div className="form__submit">
<button id="submit-btn">
Submit
</button>
</div>
</form>
</section>
);
}
이때 서버 전송이 제대로 동작하지 않았는데, 이유는 <button> 태그의 type 속성을 명시적으로 지정하지 않았기 때문이었다. <button> 태그의 MDN에 따르면 type 속성의 기본값이 submit인데도, JSX에서는 type="submit"을 버튼 태그의 속성으로 적어주지 않으면 <form> 태그의 onSubmit 이벤트 핸들러 함수가 동작하지 않았다.
type 속성 지정으로 문제를 해결하자 데이터 전송과 화면에 새로운 데이터를 렌더링하는 것이 잘 동작했다. 하지만 다른 문제가 생겼는데 아래 캡처 이미지와 같이 input 입력값을 서버에 전송한 이후 화면에 전송한 데이터가 그대로 남아있었다. 이벤트 핸들러에서 event.preventDefault()로 인해 페이지 새로고침이 막혀있는데 서버 전송 이후 아무런 조치도 해주지 않았으니 당연한 결과였다.
바닐라 JS에서는 새로운 데이터를 변수에 잘 담은 다음, 해당 input 태그의 value 값을 빈 문자열('')로 재할당을 해주면 간단히 해결되는 문제였다. 하지만 리액트에서는 같은 방식으로 문제를 해결할 수 없었고, input 태그의 입력값을 state로 관리해줘야 했다.
function Form({ addDiscussion }) {
const [author, setAuthor] = useState("");
const [title, setTitle] = useState("");
const [bodyText, setBodyText] = useState("");
const handleNewDiscussion = (event) => {
event.preventDefault();
const author = event.target[0].value;
const title = event.target[1].value;
const bodyText = event.target[2].value;
addDiscussion({ author, title, bodyText });
setAuthor("");
setTitle("");
setBodyText("");
};
return (
// 생략
<input
type="text"
name="author"
id="author"
placeholder="ex) kimcoding"
value={author}
required
/>
// 생략
)
}
위와 같이 useState로 input 데이터들을 관리하는 코드를 새로 추가했고, return 문에서는 3개의 <input> 태그의 value 값으로 state를 연결해주었다.
그러자 콘솔창에 다음과 같은 경고가 떴다.
<input> 태그에 value만 달고 onChange 핸들러를 달아주지 않아서 input 창이 데이터를 입력할 수 없는 readOnly 상태로 바뀌어버린 것이다.
// onChange 이벤트 핸들러 추가
const onChangeAuthor = (e) => {
setAuthor(e.target.value);
};
const onChangeTitle = (e) => {
setTitle(e.target.value);
};
const onChangeBodyText = (e) => {
setBodyText(e.target.value);
};
return (
// 생략
<input
type="text"
name="author"
id="author"
placeholder="ex) kimcoding"
value={author}
onChange={onChangeAuthor}
required
/>
// 생략
)
위와 같이 onChange 이벤트 핸들러를 추가해주고, input 태그와 연결해주었더니 콘솔 경고창이 사라졌다. 그리고 아래 캡처 이미지와 같이 submit 버튼을 눌러서 서버에 form 데이터를 전송한 후 setAuthor("") 등 setState 상태 변경 함수가 동작해 새로고침한 것처럼 input 창을 초기 상태로 되돌릴 수 있게 되었다.
'돌멩이 하나 > 에러는 미래의 연봉' 카테고리의 다른 글
[React] useEffect hook에서 async/await 쓰는 방법 (0) | 2023.01.09 |
---|---|
배열의 push() 메소드는 무엇을 return할까? (0) | 2022.12.27 |
export와 import : 로컬 vs. 서버 (0) | 2022.12.04 |
[React] map() 메소드로 여러 개의 html 엘리먼트 표시할 때 JSX key 속성과 싸운 이야기 (0) | 2022.11.30 |
나만의 아고라 스테이츠 만들기 과제 되돌아보기 (3) | 2022.11.20 |