React _ state & props 2

이승미·2022년 6월 9일
0

이제 본격적으로 state & props를 실습해보자.
react twittler 스프린트의 어려웠던 부분을 다시 공부하면서 복습한다.

state & props 활용해 tweet 전송 form 만들기

1. useState로 state 변수와 state 갱신 함수 만들기

  const [username, setUsername] = useState("parkhacker");
  // 트윗 유저 이름 , 유저 이름 갱신 함수
  const [msg, setMsg] = useState("");
  // 트윗 메시지, 메시지 갱신 함수
  const [data, setData] = useState(dummyTweets);
  // 버튼 통해서 새로운 트윗 추가되었을 때의 트윗 모음, 트윗 모음 갱신 함수

우선, 어떤 것을 state로 관리해야 할지를 결정해야 한다. 앞선 블로그에서 state는 내부에서 변할 수 있는 값, 즉 '상태'를 다룬다고 하였다. 그렇다면 내부에서 변하는 값에는 어떤 것들이 있을까?

바로 입력 폼을 통해 입력받는 트윗 작성자, 트윗 내용, 그리고 트윗 리스트(모음)이다.
트윗 작성자나 트윗 내용의 경우, 입력폼을 통해 사용자에게 입력 받을 때마다 값이 그때그때 값이 바뀌므로 state로 관리해야 한다.
트윗 리스트 또한 버튼을 통해 새로운 트윗이 추가될 때마다 값이 바뀌므로 state로 관리해야 한다.


2. 트윗 추가 함수 만들기

 const handleButtonClick = (event) => {
    const tweet = {
      id: data.length + 1,
      username: username, //state 변수 username
      picture: `https://randomuser.me/api/portraits/women/.jpg`,
      content: msg,  //state 변수 msg
      createdAt: new Date().toLocaleDateString("ko-kr"),
      updatedAt: new Date().toLocaleDateString("ko-kr"),
    };
    let addTweet = [tweet, ...data];
    setData(addTweet);
  };

이제 트윗 추가 함수를 만들어보자. 우선 입력폼을 통해 추가가 될 트윗을 만들어준다. 이 때, 기존의 dummyTweets와 구조가 똑같아야만 제대로 추가가 되므로 주의해야 한다.

tweet 객체를 만들어준 후에 이 트윗을 기존의 dummyTweets에 추가해주는 방법에는 unshift를 사용하는 방법, spread 를 사용하는 방법 총 2가지가 있다.


unshift를 사용할 경우
const newTweets = data.slice(); // 트윗 리스트 들어있는 data 복사하기
newTweets.unshift(newTweets);
setTweets(newTweets);

이전 블로그에서도 얘기했듯이, useState에 unshift를 사용하는 것은 강제로 값을 변경하는 것이므로 안된다.
그래서 slice 메서드를 통해 state 변수 data를 복사하고 unshift 해준다.
이렇게 해주면 강제로 값을 변경하는 것이 아니고 newTweets 라는 변수를 선언해 새로운 배열을 갱신해준 것이므로 주소값이 바뀌어(주소값이 바뀔때만 리렌더링되므로) 제대로 작동한다.

spread를 사용할 경우

const newTweets = [tweet, ...data];
setData(newTweets);

이번에는 spread를 사용하는 방법이다. 우리가 만든 tweet은 객체 상태이지만 data의 초기값인 dummyTweets는 배열 안에 객체가 있는 상태이다. 즉, tweet이 제대로 추가될 수가 없다.

그래서 spread 연산자를 이용해 newTweets 변수에 우리가 만든 tweet을 추가해준 다음, 배열이 풀어진 data 객체를 추가해준다.

이 방법이 unshift 방법보다 훨씬 더 편하다!!


3. usernameme, msg 갱신 함수 만들어주기

const handleChangeUser = (event) => {
    setUsername(event.target.value);
  };

  const handleChangeMsg = (event) => {
    setMsg(event.target.value);
  };

state 변수 username과 msg를 갱신해줄 함수도 만들어주어야 한다. 입력폼을 통해 입력하는 값이 event.target.value에 담기므로 갱신 함수 안에 event.target.value를 넣어준다.


4. 유저이름과 트윗 내용을 작성할 수 있는 input, textarea를 만들어주고 각각의 value, onChange에 state 변수, 갱신 함수 넣어주기.

<div className="tweetForm__input">
                <input
                  type="text"
                  defaultValue="parkhacker"
                  placeholder="your username here.."
                  className="tweetForm__input--username"
                  value={username}
                  onChange={handleChangeUser}
                ></input>
                <textarea
                  className="tweetForm__input--message"
                  value={msg}
                  onChange={handleChangeMsg}
                ></textarea>
              </div>

value와 onChange에 각각 state 변수, 갱신 함수를 넣어준다.
value에는 사용자가 유저 네임과 트윗 내용이 담긴다.
onChange를 통해 그 내용이 변경될 때마다 갱신 함수가 실행된다.

  <button className="tweetForm__submitButton" onClick={handleButtonClick}>Tweet </button>

버튼도 똑같이 만들어준다.

5. map 메서드를 이용해 data를 렌더링해준다.

 <ul className="tweets">
        {/* TODO : 하나의 트윗이 아니라, 주어진 트윗 목록(dummyTweets) 갯수에 맞게 보여줘야 합니다. */}
        {data.map((tweet) => (
          <div key={tweet.id}>
            <Tweet tweet={tweet} />
          </div>
        ))}
      </ul>

앞선 블로깅에서 map은 여러 개의 데이터를 렌더링할 때 사용하는 메서드라고 하였다. (map을 사용하지 않으면 각각의 데이터를 일일히 렌더링해야 한다.)

그러므로 map 메서드를 사용하여 data를 렌더링해준다. map 메서드를 사용할 때 최상위 엘리먼트에 꼭 key를 지정해주는 것을 잊어서는 안된다.


Advanced _ 트윗 필터링 구현하기

드롭다운 메뉴의 username을 선택했을 때 해당 username이 작성한 트윗만 필터링 되도록 하는 기능을 구현해보자.

1. select와 option 만들기

 <select>
   <option>--click to filter tweets by username--</option>
</select>

우선, select와 option을 사용하여 드롭다운을 만들어준다.

2. useState로 state 변수와 state 갱신 함수 만들기

  const [isFiltered, setIsFiltered] = useState(false);
  // option 창(기본값? option 누른 값?), option 창 갱신 함수, 초기값은 false
  const [filteredTweets, setFilteredTweets] = useState(dymmyTweets);
  // 트윗 목록, 트윗 목록 갱신 함수 , 초기값은 dummyTweets(초기값은 필터링X)

그 다음으로는 어떤 것을 state로 관리해야 하는지를 결정해야 한다. state로 관리가 필요한 것은 2가지가 있다.

  1. 사용자가 드롭다운 기본 값에 있는지 / 드롭다운의 option을 누른 상태인지
  2. 전체 트윗 목록 / 필터링된 트윗 목록

3. map 메서드를 이용해 username을 렌더링해준다.

const filteredName = tweets.map((el) => el.username);

.....

<select>
   <option>--click to filter tweets by username--</option>
   {filteredName.map((el, idx) => {
     <option value={el} key={idx}>{el}</option>
  })}
</select>

map 메서드를 사용하여 data를 렌더링해준다. key로는 idx를 준다.
우리는 option에 트윗 전체를 가져오는 것이 아닌, 트윗 작성자 이름만 가져오면 되기 때문에 map 메서드를 이용하여 트윗 작성자의 이름만 가져온다. 이제 드롭다운 버튼이 완성되었다.

4. 트윗이 필터링되도록 만들어준다.

const handleFillter = (event) => {
  if (event.target.value === 'cola') {
    setData(tweets)
    setFiltered(false)
  }
}

우선 필터링이 되지 않았을 때 작동하는 함수가 필요하다. event.target.value의 값이 'cola'일 경우, 전체 트윗 목록이 보여야하며, 드롭다운의 option은 눌리지 않은 상태이다.

else {
  const filtered = data.filter((tweet) => {
    tweet.username === evnet.target.value
  })
  
  setFilteredTweets(filtered)
  setFiltered(true)
}

tweet의 username이 event.target.value일 때, 필터링이 되도록 filter 메서드를 사용한다. 이 경우 트윗 목록은 username이 event.target.value인 트윗만 필터링 되어야 하며, 드롭다운의 option은 눌린 상태이다.

5. 이제 select에 state 값과 함수를 넣어주고 map 메서드를 이용해 filteredTweets를 렌더링해준다.

<select onChange={handleFilter}>
   <option value='cola'>--click to filter tweets by username--</option>
   {filteredName.map((el, idx) => {
     <option value={el} key={idx}>{el}</option>
  })}
</select>

.....

{isFiltered ? //트윗은 필터링되었나?
        filteredTweets.map((tweet) => {
          return <Tweet tweet={tweet} key={tweet.id} />
        }) // 필터링 되었을 때는 filteredTweets 를 렌더링해준다.
        :
        tweets.map((tweet) => {
          return <Tweet tweet={tweet} key={tweet.id} />
        }) // 필터링되지 않았을 때는 tweet 목록 전체를 렌더링해준다.
      }

select에 state 변수의 값과 갱신 함수를 넣어주고, 삼항 연산자와 map 메서드를 이용해서 필터링된 트윗 목록과 전체 트윗 목록을 렌더링해준다.


Advanced _ 트윗 삭제 구현하기

각각의 트윗의 휴지통 아이콘을 눌렀을 때 트윗이 삭제되는 기능을 구현해보자.

1. 트윗 삭제 함수 뼈대부터 만들어주기

const handleDelete = (username, idx) => {
  // 트윗 삭제 함수 handleDelete
  console.log(username, idx);
}

일단 트윗을 삭제해주는 함수 handleDelete의 뼈대부터 만들어준다. 트윗을 삭제하기 위해서는 username과 그 username의 트윗 목록 idx가 필요하다. (그 username의 모든 트윗을 지우는 것이 아니라 각각의 삭제하고자 하는 트윗만 삭제하는 것이므로)

2. handleDelte와 idx를 props로 내려주기

{isFiltered ? //트윗은 필터링되었나?
        filteredTweets.map((tweet) => {
          return <Tweet tweet={tweet} key={tweet.id} 
          handleDelete = {handleDelte} idx={idx} />
        }) // 필터링 되었을 때는 filteredTweets 를 렌더링해준다.
        :
        tweets.map((tweet) => {
          return <Tweet tweet={tweet} key={tweet.id} 
          handleDelete = {handleDelte} idx={idx} />
        }) // 필터링되지 않았을 때는 tweet 목록 전체를 렌더링해준다.
      }

우리는 handleDelete를 전체 트윗이 아니라 각각의 트윗에 적용하여 사용하고 싶다. 그렇다면 props로 내려주어야 한다. (각각의 트윗에 각각의 props가 적용이 된다) 아까전의 isFiltered 삼항연산자 부분에 'handleDelete = {handleDelte} idx={idx}'를 각각 추가해주자.

const Tweet = ({ tweet , handleDelete, idx}) => {

그리고 tweet.js의 props 부분에 handleDelete와 idx를 추가해준다.

3. 트윗 삭제해줄 버튼 만들기

<button onClick = {() => handleDelete(tweet.username, idx)}></button>

버튼을 눌렀을 때만 handleDelete 함수가 실행되도록 함수형으로 넣어준다. 이렇게 만들고 버튼을 눌러보면 아까 함수 안에 console.log를 넣어놓았기 때문에 username과 username이 있는 객체의 인덱스 번호가 뜬다. 우리는 이 값을 가지고 삭제시켜 줄것이다.

4. 트윗 삭제 함수 완성시키기

const handleDelete = (username, deleteIdx) => {
  const deletes = tweet.filter((tweet) => idx !== deleteIdx);
  // deleteIdx가 idx가 아닌 모든 트윗들을 deletes 변수에 다 넣어준다! (그것이 삭제되는 것)
  setData(deletes)
  // deletes에 해당 idx가 아닌 모든 트윗 다 넣어주고 리렌더링한다
}

이제 마지막으로 handleDelte 함수를 완성시켜주자. 우선 함수의 argument에 기존의 idx를 deleteIdx로 바꿔준다. 우리는 idx와 deleteIdx를 비교해서 deletes 변수에 트윗들을 담아주어야 하기 때문이다.

그리곤 filter 메서드를 통해 deleteIdx를 제외한 모든 idx의 트윗을 deletes 변수에 저장해준다. 이렇게 되면 해당 deleteIdx의 트윗을 제외하고 모든 트윗들이 담기게 되므로 deleteIdx의 트윗이 삭제되는 것이라고 할 수 있다.

마지막으로 setData에 deletes를 넣어서 리렌더링해준다.

0개의 댓글