TC - 4번일지 (상태관리 1)

Debug-Life ·2023년 3월 2일
0

8-5. 조건부 로딩

순서가 갑자기 조금 뒤죽박죽 됐지만 https://velog.io/@dlife/react-Twitter-project-summary-2 전전글의 8번 State의 다른 추가적 기능 설명이다.

목표 : 특정 상황에 따라서 모달창 껐다 켜주기. 즉, 사용자가 특정 동작을 할 때마다 렌더링을 다시 해주는것이다. 역시나 state를 이용한다.

이를 위해 해야할 것은 3가지다.

  1. useState()로 상태를 등록
  2. 백드롭에 이벤트 리스너를 추가한다음
  3. 등록한 상태를 사용해 모달을 표시하거나 숨기기

이전에 했던것과 별차이 없다

단 useState 기본값을 true로 해야한다. 이 상태 값으로 관리하려는 건 텍스트가 아니라서. 이렇게 하면 모달이 표시된 상태로 시작될거다.

이제 1번을 끝냈으니 Modal.jsx 에 이벤트를 등록할 차례다. 다만 클릭이벤트 리스너에 등록할 함수는 Modal 컴포넌트가 아니라 PostList 컴포넌트다. 왜냐면 그곳에 상태 정의가 있으니까.


✍ 변경 한 코드


8-5-1. 실제로 상태값에 따라 사용

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> */}
    </>
  );
}

8-5-2. 두가지 다른 방식

  1. 변수 하나에 jsx 코드를 넣어서 가독성 좋게.
    개인적으로는 이 방식을 선호한다.

1) 변수

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> */}
    </>
  );
}

2) && 연산자

개인적으로는 가독성도 좋고, 코드량도 적은 이 방식을 선호한다.

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> */}
    </>
  );
}



12. Header , newPosting 추가

헤더를 추가해서 새로운 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;



13. form전송 버튼 추가

실제로 새로운 포스트를 등록하는 버튼을 NewPost에 추가하는 기능구현

cf. 폼에 버튼을 추가하면 기본적으로 버튼이 눌렸을 때 해당 폼을 전송합니다. 그러면 submit 이벤트가 발생하게 되고 브라우저는 이 이벤트를 받으면 자동으로 HTTP 요청을 만들어 해당 웹사이트를 서비스하는 서버로 보낸다.

13.1. cancel 버튼 구현

그런데 cancel 버튼엔 HTTP 요청을 막아줄것이다. 서버와 동작하는게 아닌 클라이언트 사이드에서 처리할 것이기 때문에.

  1. 버튼의 type 속성을 'button'으로 지정
    그러니까 type 속성을 'button'으로 지정하지 않으면 저절로 type 속성을 'submit'이 된다.

✍ 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>
  );
}



  • onCancel={onStopPosting} 하나만 추가

✍ 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;



13.2. summit 버튼 구현

  1. modal 창 닫기
  2. NewPoost 컴포넌트에 입력한 본문과 저자이름 데이터를 가져와야함.
  3. 목록에 post 추가하기 (동적으로 출력되게)

1) form에 onSubmit 이벤트 추가(브라우저 기본지원)

기본적으로 form을 전송하면 Submit 이벤트가 트리거되고, 그럼 브라우저는 자동으로 HTTP 요청을 만들어 전송하는데 위에서 말한것처럼 이 동작을 안하려고함.
이 리액트 앱을 서비스하는 서버로 HTTP 요청을 보내면 페이지가 다시 로딩될 텐데 그 요청을 처리할 서버 쪽 코드가 현재는 없음. 리액트는 서버가 아니라 브라우저에서 동작하는 프런트엔드 라이브러리라서 그런 요청을 처리할 수 없음.

그래서 submit 이벤트에서 내장 메서드인 'preventDefault()' 호출 : 브라우저가 자동으로 HTTP 요청을 만들어 전송하는 걸 막음

2) 입력값 관리 코드 이전

그런데 이 작업을 하기 전에 우선 입력값을 관리해주는 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;



3) useState 상태 업데이트 함수 (중요)

이 아래 NewPost.jsx 부분이 중요한데 setPosts 함수의 매개변수로 화살표 함수를 줬다. 그리고 existingPostsposts 는 같은 이전 상태값으로 쓰였다. 그러니까 변수명만 다를뿐, 똑같은 값이 들어있다.

리액트 훅인 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;





profile
인생도 디버깅이 될까요? 그럼요 제가 하고 있는걸요

0개의 댓글