순서가 갑자기 조금 뒤죽박죽 됐지만 https://velog.io/@dlife/react-Twitter-project-summary-2 전전글의 8번 State의 다른 추가적 기능 설명이다.
목표 : 특정 상황에 따라서 모달창 껐다 켜주기. 즉, 사용자가 특정 동작을 할 때마다 렌더링을 다시 해주는것이다. 역시나 state를 이용한다.
이를 위해 해야할 것은 3가지다.
- useState()로 상태를 등록
- 백드롭에 이벤트 리스너를 추가한다음
- 등록한 상태를 사용해 모달을 표시하거나 숨기기
이전에 했던것과 별차이 없다
단 useState 기본값을 true로 해야한다. 이 상태 값으로 관리하려는 건 텍스트가 아니라서. 이렇게 하면 모달이 표시된 상태로 시작될거다.
이제 1번을 끝냈으니 Modal.jsx 에 이벤트를 등록할 차례다. 다만 클릭이벤트 리스너에 등록할 함수는 Modal 컴포넌트가 아니라 PostList 컴포넌트다. 왜냐면 그곳에 상태 정의
가 있으니까.
✍ 변경 한 코드
modalIsVisible 이 true면 렌더링하고 false면 렌더하지 않게해서
turn on/off 구현
✍ PostList.jsx
function PostList() {
// const [modalIsVisible, setModalIsVisible] = useState(true);
// const [enteredBody, setEnteredBody] = useState("");
// const [enteredAuthor, setEnteredAuthor] = useState("");
// function hideModalHandler() {
// setModalIsVisible(false);
// }
// function bodyChangeHandler(event) {
// setEnteredBody(event.target.value);
// }
// function authorChangeHandler(event) {
// setEnteredAuthor(event.target.value);
// }
return (
<>
{modalIsVisible ? (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
) : null}
{/* <ul className={classes.posts}>
<Post author={enteredAuthor} body={enteredBody} />
<Post author="Manel" body="the second props pratice!" />
</ul> */}
</>
);
}
function PostList() {
// const [modalIsVisible, setModalIsVisible] = useState(true);
// const [enteredBody, setEnteredBody] = useState("");
// const [enteredAuthor, setEnteredAuthor] = useState("");
// function hideModalHandler() {
// setModalIsVisible(false);
// }
// function bodyChangeHandler(event) {
// setEnteredBody(event.target.value);
// }
// function authorChangeHandler(event) {
// setEnteredAuthor(event.target.value);
// }
let modalContent;
if (modalIsVisible) {
modalContent = (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
);
}
return (
<>
{modalContent}
{/* <ul className={classes.posts}>
<Post author={enteredAuthor} body={enteredBody} />
<Post author="Manel" body="the second props pratice!" />
</ul> */}
</>
);
}
개인적으로는 가독성도 좋고, 코드량도 적은 이 방식을 선호한다.
function PostList() {
// const [modalIsVisible, setModalIsVisible] = useState(true);
// const [enteredBody, setEnteredBody] = useState("");
// const [enteredAuthor, setEnteredAuthor] = useState("");
// function hideModalHandler() {
// setModalIsVisible(false);
// }
// function bodyChangeHandler(event) {
// setEnteredBody(event.target.value);
// }
// function authorChangeHandler(event) {
// setEnteredAuthor(event.target.value);
// }
return (
<>
{modalIsVisible && (
<Modal onClose={hideModalHandler}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
)}
{/* <ul className={classes.posts}>
<Post author={enteredAuthor} body={enteredBody} />
<Post author="Manel" body="the second props pratice!" />
</ul> */}
</>
);
}
헤더를 추가해서 새로운 Post를 생성할 수 있게 버튼에 기능을 추가해준다.
역시나 또 state를 사용하는데 이전에 했던 상태 올리기로 한다.
PostList
컴포넌트에 있던 상태를 상위 컴포넌트인 App.jsx
로 옮긴다.
✍ PostList.jsx
import 문 중략,,
function PostList({ isPosting, onStopPosting }) {
// const [enteredBody, setEnteredBody] = useState("");
// const [enteredAuthor, setEnteredAuthor] = useState("");
// function bodyChangeHandler(event) {
// setEnteredBody(event.target.value);
// }
// function authorChangeHandler(event) {
// setEnteredAuthor(event.target.value);
// }
return (
<>
{isPosting && (
<Modal onClose={onStopPosting}>
{/* <NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
/>
</Modal>
)}
<ul className={classes.posts}>
<Post author={enteredAuthor} body={enteredBody} />
<Post author="Manel" body="the second props pratice!" />
</ul>
</>
);
}
export default PostList; */}
✍ App.jsx
import { useState } from "react";
import PostList from "./components/PostList";
import MainHeader from "./components/MainHeader";
function App() {
const [modalIsVisible, setModalIsVisible] = useState(false);
function showModalHandler() {
setModalIsVisible(true);
}
function hideModalHandler() {
setModalIsVisible(false);
}
return (
<>
<MainHeader onCreatePost={showModalHandler} />
<main>
<PostList isPosting={modalIsVisible} onStopPosting={hideModalHandler} />
</main>
</>
);
}
export default App;
마지막으로 modal 가시성을 위해서 onClose 프로퍼티를 클릭이벤트핸들러에 추가.
✍ Modal.jsx
import classes from "./Modal.module.css";
function Modal(props) {
return (
<>
<div className={classes.backdrop} onClick={props.onClose} />
<dialog open={true} className={classes.modal}>
{props.children}
</dialog>
</>
);
}
export default Modal;
실제로 새로운 포스트를 등록하는 버튼을 NewPost에 추가하는 기능구현
cf. 폼에 버튼을 추가하면 기본적으로 버튼이 눌렸을 때 해당 폼을 전송합니다. 그러면 submit 이벤트가 발생하게 되고 브라우저는 이 이벤트를 받으면 자동으로 HTTP 요청을 만들어 해당 웹사이트를 서비스하는 서버로 보낸다.
그런데 cancel 버튼엔 HTTP 요청을 막아줄것이다. 서버와 동작하는게 아닌 클라이언트 사이드에서 처리할 것이기 때문에.
✍ NewPost.jsx
//import classes from "./NewPost.module.css";
function NewPost(props) {
//return (
//<form className={classes.form}>
{/* <p>
<label htmlFor="body">Text</label>
<textarea id="body" required rows={3} onChange={props.onBodyChange} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" required onChange={props.onAuthorChange} />
</p> */}
<p className={classes.actions}>
<button type="button" onClick={props.onCancel}>
Cancel
</button>
<button>Sumbit</button>
</p>
</form>
);
}
✍ PostList.jsx
// 중략
function PostList({ isPosting, onStopPosting }) {
//중략
return (
<>
{isPosting && (
<Modal onClose={onStopPosting}>
<NewPost
onBodyChange={bodyChangeHandler}
onAuthorChange={authorChangeHandler}
onCancel={onStopPosting}
/>
</Modal>
)}
////// <ul className={classes.posts}>
//// <Post author={enteredAuthor} body={enteredBody} />
// <Post author="Manel" body="the second props //pratice!" />
// </ul>
// </>
);
}
//export default PostList;
- modal 창 닫기
- NewPoost 컴포넌트에 입력한 본문과 저자이름 데이터를 가져와야함.
- 목록에 post 추가하기 (동적으로 출력되게)
기본적으로 form을 전송하면 Submit 이벤트가 트리거되고, 그럼 브라우저는 자동으로 HTTP 요청을 만들어 전송하는데 위에서 말한것처럼 이 동작을 안하려고함.
이 리액트 앱을 서비스하는 서버로 HTTP 요청을 보내면 페이지가 다시 로딩될 텐데 그 요청을 처리할 서버 쪽 코드가 현재는 없음. 리액트는 서버가 아니라 브라우저에서 동작하는 프런트엔드 라이브러리라서 그런 요청을 처리할 수 없음.
그래서 submit 이벤트에서 내장 메서드인 'preventDefault()'
호출 : 브라우저가 자동으로 HTTP 요청을 만들어 전송하는 걸 막음
그런데 이 작업을 하기 전에 우선 입력값을 관리해주는 PostList.jsx
에서 하위 스크립트이자 현재 작업할 NewPost.jsx
로 코드를 옮겨올 것임.
그러면 폼을 전송하더라도 전송이 안될 것이고,
이후 전송을 했으므로 업데이트가 됐을 것이므로
객체에 새롭게 업데이트된 본문과 저자이름 데이터를 담음.
그리고 폼전송을 해서 업데이트 된 객체가 담겼는지 확인을 종종해야함. console.log 를 찎어봐서 확인하기.
✍ NewPost.jsx
import { useState } from "react";
import classes from "./NewPost.module.css";
function NewPost(props) {
const [enteredBody, setEnteredBody] = useState("");
const [enteredAuthor, setEnteredAuthor] = useState("");
function bodyChangeHandler(event) {
setEnteredBody(event.target.value);
}
function authorChangeHandler(event) {
setEnteredAuthor(event.target.value);
}
function submitHandler(event) {
event.preventDefault();
const postData = {
body: enteredBody,
author: enteredAuthor,
};
console.log(postData);
props.onCancel();
}
return (
<form className={classes.form} onSubmit={submitHandler}>
<p>
<label htmlFor="body">Text</label>
<textarea id="body" required rows={3} onChange={bodyChangeHandler} />
</p>
<p>
<label htmlFor="name">Your name</label>
<input type="text" id="name" required onChange={authorChangeHandler} />
</p>
<p className={classes.actions}>
<button type="button" onClick={props.onCancel}>
Cancel
</button>
<button>Sumbit</button>
</p>
</form>
);
}
export default NewPost;
이 아래
NewPost.jsx
부분이 중요한데setPosts 함수
의 매개변수로 화살표 함수를 줬다. 그리고existingPosts
과posts
는 같은 이전 상태값으로 쓰였다. 그러니까 변수명만 다를뿐, 똑같은 값이 들어있다.리액트 훅인 useState 에서 두번쨰 인자로 들어온 함수는 상태를 업데이트 해주는 함수이고, 이 함수의 매개변수는 이전상태값이다. 즉 ,
posts
이다.
setPosts 의 매개변수로 화살표 함수로 변경헀음.
이유 : 새로운 상태 값이 이전 상태 값을 바탕으로 한 것이기 떄문에.
왜냐하면 왜냐하면 리액트 내부에서는 상태 갱신 함수를 곧바로 실행하는 게 아니기 때문. 단지 상태 갱신을 예약해 둘 뿐.
그럼 여러 업데이트가 서로 얽혀서 일어날 때 잘못된 상태 갱신을 할 수도 있음. 그래서 리액트가 상태를 가져올떄 그 즉시 이전상태를 가져와 새 상태로 업데이트 하는것임.
✍ NewPost.jsx
import { useState } from "react";
import Post from "./Post";
import NewPost from "./NewPost";
import Modal from "./Modal";
import classes from "./PostList.module.css";
function PostList({ isPosting, onStopPosting }) {
const [posts, setPosts] = useState([]);
function addPostHandler(postData) {
setPosts((existingPosts) => [postData, ...existingPosts]);
}
return (
<>
{isPosting && (
<Modal onClose={onStopPosting}>
<NewPost onCancel={onStopPosting} onAddPost={addPostHandler} />
</Modal>
)}
<ul className={classes.posts}>
<Post author="Manel" body="the second props pratice!" />
</ul>
</>
);
}
export default PostList;