엔터키 이벤트에서의 리렌더링 문제 해결하기

이 부분에서 스프린트를 진행하다가, 테스트 통과는 완료가 되었는데,
기능이 원하는 대로 실행이 안되었다.

원하는 기능
1) 글자를 입력한다
2) 엔터키를 누른다: 글자를 하나 이상 입력하고, 엔터키를 눌렀을 때만 태그가 추가되어야 한다.

내가 구현한 기능
1) 글자를 입력한다
2) 엔터키를 누른다:
엔터키를 눌렀다 뗐을 때 바로 화면에 태그가 추가가 안되었다.
이후 다른 알파벳 키를 눌렀을 때만 화면에 태그가 추가되는 것이 보였다.

이 부분 때문에 페어분과 30-40분은 고민을 했던 것 같다.

문제의 코드는 다음과 같았다.

...전략
export const Tag = () => {
  const initialTags = ['CodeStates', 'kimcoding'];

  const [tags, setTags] = useState(initialTags);
  const [value,setValue] = useState('');

  const handleChangeValue = (event) => {
    setValue(event.target.value);
  }

  const removeTags = (indexToRemove) => {
    // TODO : 태그를 삭제하는 메소드를 완성하세요.
    setTags(tags.filter((el,index) => index !== indexToRemove))
  };
  
  const addTags = (event) => {
    
    console.log("value",event.target.value,"키",event.key)
    if((event.key === "Enter") && (!tags.includes(event.target.value)) && (event.target.value !== '')){
      tags.push(event.target.value);
      event.target.value = '';
    }
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요. 
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 엔터키를 눌렀을 때만 태그 추가하기
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    }
  

  return (
    <>
      <TagsInput>
        <ul id='tags'>
          {tags.map((tag, index) => (
            <li key={index} className='tag'>
              <span className='tag-title'>{tag}</span>
              <span className='tag-close-icon' onClick = {() => removeTags(index)}>
                {/* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                            삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
                &times;
              </span>
            </li>
          ))}
        </ul>
        <input
          value = {value}
          className='tag-input'
          type='text'
          onKeyUp={(event) => {
            if(event.key === "Enter"){
              console.log("값:",event.target.value,"누른키:", event.key)
              addTags(event)
            }
          }}
          onChange= {handleChangeValue}
          placeholder='Press enter to add tags'
        />
      </TagsInput>
    </>
  );
};

문제가 생긴 원인 분석하기

문제의 원인은 다음 코드에 있다.

 const addTags = (event) => {
    
    console.log("value",event.target.value,"키",event.key)
    if((event.key === "Enter") && (!tags.includes(event.target.value)) && (event.target.value !== '')){
      **_tags.push(event.target.value);_** //문제의 원인
      event.target.value = '';
    }
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요. 
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 엔터키를 눌렀을 때만 태그 추가하기
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    }

addTags함수는 엔터키를 눌렀을 때만 실행이 되는 함수다.
그리고 이 함수를 실행했을 때(엔터키를 눌렀다가 뗐을 때) 바로 태그가 추가된게 화면에 보여야한다
(리액트에서는 이를 리렌더링이라고 한다)

리렌더링과 관련된 가장 중요한 개념은 useState에 있다. useState에서는 상태갱신함수만을 통해서 상태 변수를 업데이트해준다.
그런데 내가 작성한 코드에서는 상태갱신함수인 setTags를 사용하지 않고 직접적으로 상태변수 tags를 변형시켜줬다. 그것도 원본 배열을 변형시켰다. 그러니 될 리가 없지.!!!!!!!!
그러면 여기서는 상태갱신함수 setTags([newtag,...Tags]) 이렇게 수정이 되어야한다.

그러면 왜 새 글자를 입력했을 때 렌더링이 된건가?
다음 코드에 원인이 있었다.

<input
          value = {value}
          className='tag-input'
          type='text'
          onKeyUp={(event) => {
            if(event.key === "Enter"){
              console.log("값:",event.target.value,"누른키:", event.key)
              addTags(event)
            }
          }}
          onChange= {handleChangeValue}
          placeholder='Press enter to add tags'
        />

새 글자를 입력하는 순간에는 handleChangeValue가 실행된다. 그러면 상태갱신함수가 실행이 되며, 화면이 리렌더링된다. 이때 이전에 내부적으로 업데이트되었던 Tags 배열도 같이 리렌더링 되면서 그제서야 태그 추가 효과가 나타나는 것이다.

에러 수정한 코드는 다음과 같다.

const addTags = (event) => {
    console.log(event.target)
    if((event.key === "Enter") && (!tags.includes(event.target.value)) && (event.target.value !== '')){
      const newTag = event.target.value;
      setTags([...tags,newTag])
      setValue('');
    }
    // TODO : tags 배열에 새로운 태그를 추가하는 메소드를 완성하세요. 
    // 이 메소드는 태그 추가 외에도 아래 3 가지 기능을 수행할 수 있어야 합니다.
    // - 엔터키를 눌렀을 때만 태그 추가하기
    // - 이미 입력되어 있는 태그인지 검사하여 이미 있는 태그라면 추가하지 말기
    // - 아무것도 입력하지 않은 채 Enter 키 입력시 메소드 실행하지 말기
    // - 태그가 추가되면 input 창 비우기
    }
  

  return (
    <>
      <TagsInput>
        <ul id='tags'>
          {tags.map((tag, index) => (
            <li key={index} className='tag'>
              <span className='tag-title'>{tag}</span>
              <span className='tag-close-icon' onClick = {() => removeTags(index)}>
                {/* TODO :  tag-close-icon이 tag-title 오른쪽에 x 로 표시되도록 하고,
                            삭제 아이콘을 click 했을 때 removeTags 메소드가 실행되어야 합니다. */}
                &times;
              </span>
            </li>
          ))}
        </ul>
        <input
          value = {value}
          className='tag-input'
          type='text'
          onKeyUp={addTags}
          onChange= {handleChangeValue}
          placeholder='Press enter to add tags'
        />
      </TagsInput>
    </>
  );
};

에러 수정하다보니, 엔터키 누르고 화면에서 입력한 글씨 지우는 곳에서도 같은 실수를 했었다.
전체 코드를 보느라고 상태갱신함수의 목적을 잊고 썼는데, 정말 주의해야겠다.
이부분을 짚어주신 페어분께 감사했다...

좋은 웹페이지 즐겨찾기