본문 바로가기
웹 프로그래밍/🌜꿈 일기장

[React] 꿈 일기 기록 사이트 제작 일지-10. 사용자가 장르를 수정할 수 있는 `사용자 장르 추가/삭제 기능` 구현 (+ localStorage)

by 청량리 물냉면 2023. 9. 27.
반응형

기본적인 CRUD 기능은 완성되었고 이제 추가적인 기능을 구현하려고 한다.

꿈은 꿀 때마다 내용이 달라져서 마치 영화를 보는 것 같다는 생각을 한 적이 있다. 이런 생각을 기반으로 꿈일기에도 내가 꾼 꿈의 장르를 설정해보면 재미있겠다는 생각을 했다.

장르는 기본적으로 몇 가지가 주어지는데 이것만으로 부족하다고 느껴지는 사용자도 있을 것 같아 사용자가 장르를 추가하고 삭제하여 일기에 적용하도록 추가 기능을 구현하기로 했다.

 

😀 구현 시작

장르추가 버튼은 수정하기 페이지 > 장르|제목 옆에 달면 좋을 것 같아서 아래와 같이 달아보았다. 

버튼이 onClick되면 genreEditroHandler라는 함수를 실행한다. 

'장르|제목' 아래에 장르를 추가할 수 있는 input 필드를 보여주는 함수이다. 

  const [genreEditorShown, setGenreEditorShown] = useState(false);
  
  ...
  
  const genreEditroHandler = () => {
    setGenreEditorShown((prev) => !prev);
  };

 

이전 상태값을 기반으로 반대 동작을 하게 구현했다.

이전값이 false인 경우 true가 되고 이전값이 true인 경우 false가 된다.

 

  const [inputData, setInputData] = useState("");
  
  ...            
  
          {genreEditorShown && (
            <div className="genre-editor">
              <input
                placeholder="장르"
                ref={userGenreRef}
                onChange={(e) => setInputData(e.target.value)}
              />
              <Button type={"plus"} text="+" onClick={addGenreHandler} />

              {userGenre.map((it, idx) => (
                <li key={idx} value={it.value}>
                  {it.name}
                  <Button
                    type={"plus"}
                    text="x"
                    onClick={() => removeGenreHandler(it.value)}
                  />
                </li>
              ))}
            </div>
          )}

JSX 코드는 위와 같이 구현했다.

addGenreHandler로 사용자에게 입력받은 값을 추가하고, removeGenreHandler로 값을 삭제한다.

 

➕ 장르 추가 addGenreHandler

값을 추가할 때, 장르 배열에 입력한 값과 동일한 값이 있을 시 추가하지 않게 하고 싶었다. 

배열 내 객체 포함여부 라는 키워드로 검색해 some()이라는 메서드를 알게 되었다.

 

[Javascript] some 배열, 객체 내 원하는 값 있는지 확인

Javacscript 배열Array 또는 객체Object 내 원하는 값 있는지 확인 배열 또는 객체 내 원하는 값이 있는지 확인할수 있는 api는 some이다. 아래 배열, 객체에서의 사용 예제를 참고하자. 사용 예 let result =

goodmemory.tistory.com

  const [userGenre, setUserGenre] = useState(genreList);
  
  ...
  
  const addGenreHandler = () => {
    const genreArr = [...genreList];
    if (!genreArr.some((it) => it.value === inputData)) {
      genreArr.push({ value: inputData, name: inputData });
      console.log(genreArr);
      setUserGenre(genreArr);
    }
    setInputData("");
    userGenreRef.current.value = null;
  };

빈 값을 추가했을 때 추가가 되는 버그와 장르를 계속해서 추가했을 때 이전 값이 대체되는 버그가 있기는 하지만 동일한 이름을 가진 옵션은 더 이상 추가되지 않는 것을 볼 수 있다. 

 

⛔ 장르 제거 removeGenreHandler

값을 제거하는 함수는 filter()를 이용해 구현했다. 

  const removeGenreHandler = (value) => {
    const genreArr = userGenre.filter((it) => it.value !== value);
    setUserGenre(genreArr);
  };

모든 값을 지울 수 없게 사용자가 등록해 놓은 값과 '일상' 값은 유지하도록 구현하는 것이 좋겠다. 

현재 유저가 사용하고 있는 genre값은 state로 지정되어 있다. 이 값과 '일상'이 value값과 동일하다면 remove를 수행하지 않게 해 주었다. 

  const removeGenreHandler = (value) => {
    if (value !== "일상" && value !== genre) {
      const genreArr = userGenre.filter((it) => it.value !== value);
      setUserGenre(genreArr);
    }
  };

 

 

📀 LocalStorage

현재는 genreList 라는 배열이 하드코딩된 상태이므로 새로고침하면 변경했던 값이 전부 사라진다. 

genre 리스트를 로컬 스토리지에 저장해서 장르 리스트 데이터 베이스처럼 이용하기로 했다. 

우선 이전에는 genreList를 배열 내 여러개의 객체로 저장했었는데 select의 option 요소 중 value, name이 동일하기 때문에 객체로 저장할 것 없이 배열 내에 원소로 장르를 모두 저장했다.

// const genreList = [
//   { value: "일상", name: "일상" },
//   { value: "판타지", name: "판타지" },
//   { value: "액션", name: "액션" },
//   { value: "모험", name: "모험" },
//   { value: "호러", name: "호러" },
// ];

const genreList = ["일상", "판타지", "액션", "모험", "호러"];

이렇게 바꾸게 되면 아래 select option 다루는 부분은 모두 수정해야 한다. 

 

              {/* <select value={genre} onChange={(e) => setGenre(e.target.value)}>
                {userGenre.map((it, idx) => (
                  <option key={idx} value={it.value}>
                    {it.name}
                  </option>
                ))}
              </select> */}
              
              
              <select value={genre} onChange={(e) => setGenre(e.target.value)}>
                {userGenre.map((it, idx) => (
                  <option key={idx} value={it}>
                    {it}
                  </option>
                ))}
              </select>

it.value, it.name과 같이 객체의 프로퍼티 키 값을 지정하지 않고, 모두 it으로 변경한다. 

이렇게 모두 변경해주고 난 후 로컬스토리지 setItem, getItem을 이용한다.

 

  const [userGenre, setUserGenre] = useState([]);
  
  ...
    
  useEffect(() => {
    const localData = localStorage.getItem("dreamNoteGenre");
    if (!localData) {
      setUserGenre(genreList);
      localStorage.setItem("dreamNoteGenre", JSON.stringify(genreList));
    } else {
      setUserGenre(JSON.parse(localData));
      console.log(userGenre);
    }
  }, []);

초기 렌더링 시 getItem()으로 로컬스토리지 값을 가져와 localData에 저장한다.

저장된 값이 없을 시 미리 장르 데이터를 몇가지 정의해 놓은 genreList를 사용해 로컬스토리지에 값을 저장해준다.

🚧
이때 주의할 점은, 위에서(setUserGenre(genreList);) userGenre의 값을 정의해 놓았다고 해서 userGenre를 이용해 로컬스토리지를 채워주면 안 된다는 점이다.
리액트는 하나의 함수 내부 계산을 함수 정의가 모두 끝나고 한번에 수행하기 때문에 함수가 끝나지 않을 때의 userGenre 값은 내가 원하는 값으로 채워져 있지 않은 상태이다. 이때의 userGenre값을 참조하면 내가 원하는 결과가 나오지 않는다.
따라서 이미 정확히 값이 정해진 데이터를 참조해야 한다. 

 

  const addGenreHandler = () => {
    if (inputData !== "") {
      const genreArr = [...userGenre];
      if (!genreArr.some((it) => it === inputData)) {
        genreArr.push(inputData);
        setUserGenre(genreArr);
        setInputData("");
        userGenreRef.current.value = null;
        localStorage.setItem("dreamNoteGenre", JSON.stringify(genreArr));
      }
    }
  };

값을 추가하는 함수에서는 공백인 경우 값이 추가되는 것을 방지하는 코드를 추가했고 가장 마지막에 새로운 값을 저장한 genreArr 데이터를 json형태로 추가했다. 

이때도 genreArr 대신 userGenre를 참조해서는 안 된다.

 

  const removeGenreHandler = (value) => {
    if (value !== "일상" && value !== genre) {
      const genreArr = userGenre.filter((it) => it !== value);
      setUserGenre(genreArr);
      localStorage.setItem("dreamNoteGenre", JSON.stringify(genreArr));
    }
  };

값을 삭제하는 함수도 마찬가지로 마지막 줄에 로컬스토리지에 값을 세팅하는 코드를 작성해 주었다.

 

🚜 결과

 

💄  스타일링

 

 


오늘 해결한 에러 

Too many re-renders. React limits the number of renders to prevent an infinite loop.

리렌더링이 너무 많이 일어날 때 발생하는 에러라고 한다. 

 

해결방법

                  <Button
                    type={"plus"}
                    text="x"
                    onClick={removeGenreHandler(it.value)}
                  />

👇

                  <Button
                    type={"plus"}
                    text="x"
                    onClick={() => removeGenreHandler(it.value)}
                  />

onClick부분에 화살표 함수를 추가해 주었다. 

remove함수 내부에서 setState하는 부분에서 무한 리렌딩이 발생하는데, 이를 화살표 함수로 래핑해주면 사용자가 클릭을 할 때만 리렌더링되므로 오류를 해결할 수 있다고 한다. 

아래 포스팅을 참고했다.

 

[리액트 에러 React Error] Too many re-renders. React limits the number of renders to prevent an infinite loop.

안녕하세요. 디자인도 하고, 개발도 하는 '디발자 뚝딱'입니다. 이번 포스팅에서는 오늘 리액트 작업 중에 발생한 에러에 대해 다뤄보겠습니다. 발생한 에러 내용은 이렇습니다. Too many re-renders.

anerim.tistory.com

 


COMMIT

 

:sparkles:사용자 장르 추가/삭제 기능 구현 · Yoonyesol/Dream_note@9b6a449

Yoonyesol committed Sep 26, 2023

github.com

 

:lipstick:장르추가 기능 스타일링 및 Button CSS 수정 · Yoonyesol/Dream_note@3c8a6de

Yoonyesol committed Sep 26, 2023

github.com

 

반응형