[항해 실전 프로젝트][React] 마이페이지 닉네임 입력 기능 구현하기

Carrie·2023년 8월 14일
0


위와 같은 구성으로 마이페이지 닉네임 입력 기능을 구현해보고자 한다!

구현 기능 설명

1. 연필 버튼을 클릭하면 span 태그가 input으로 바뀌고 edit모드가 실행된다.
2. 닉네임 입력 도중 연필 버튼이 아닌 외부 영역을 클릭(또는 터치)하면 edit모드가 종료된다.
3. 닉네임을 입력하면 실시간 유효성 검사를 실시한다.
4. 유효성 검사를 통과한 후 연필 버튼을 클릭하면 닉네임값을 저장한다.
(아직 api 연결 전이므로 우선 로컬에 저장)

1. 연필 버튼을 클릭하면 span 태그가 input으로 바뀌고 edit모드가 실행된다.

//input 값을 입력할 때마다 실시간으로 유효성 검사를 실시한다.
  const nicknameChangeUtil = (e) =>
    onChangeNicknameHandler(e, setNickname, setNicknameError);

  const nicknameSubmitHandler = () => {
    if (!isEditing) {
      setIsEditing(true); // 연필 버튼을 클릭하면 edit모드를 true로
      return;
    }
    if (isEditing) {
      // nicknameCheck 함수는 별도의 파일에 있으므로 해당 함수를 호출한다.
      const result = nicknameCheckHandler(nickname, setNicknameError);
      if (result) { // 유효성 검사를 통과하면
        setNickname(nickname); // 로컬에 닉네임을 세팅하고
        setIsEditing(false); // edit모드를 종료한다.
      }
    }
  };
<NicknameContainer>
  {isEditing ? (
    <div> // edit모드가 true이면 input 태그 노출
      <input
        type='text'
        value={nickname}
        className='nicknameInput'
        // onChange 함수는 별도의 파일에 있어 해당 함수를 호출한다.
        onChange={nicknameChangeUtil}
        onBlur={blurHandler}
        />
      {nicknameError && <small>{nicknameError}</small>}
    </div>
  ) : (
    <span>{nickname || '닉네임'}</span> // false이면 span 태그 노출
  )}
  <ImageButton onClick={nicknameSubmitHandler}> // 연필 버튼 클릭
    <img
      className='pencilButton'
      src={`${process.env.PUBLIC_URL}assets/svgs/pencil.svg`}
      alt='닉네임 바꾸기'
      />
  </ImageButton>

2. 닉네임 입력 도중 연필 버튼이 아닌 외부 영역을 클릭(또는 터치)하면 edit모드가 종료된다.

const blurHandler = (e) => {// input 태그 focus를 벗어나면 아래 로직을 실행한다.
    if (
      e.target.closest('.nicknameInput') ||
      e.target.closest('.pencilButton')
    ) {
      return;
    }
    setNickname('');
    setNicknameError('');
    setIsEditing(false);
  };

3. 닉네임을 입력하면 실시간 유효성 검사를 실시한다.

export const onChangeNicknameHandler = (e, setNickname, setNicknameError) => {
  const value = e.target.value;
  setNickname(value);
  nicknameCheckHandler(value, setNicknameError);
};

export const nicknameCheckHandler = (nickname, setNicknameError) => {
    const nicknameCheck = /^.{1,10}$/;
    if (nickname === '') {
        setNicknameError('닉네임을 입력해주세요.');
        return false;
    } else if (!nicknameCheck.test(nickname)) {
        setNicknameError('닉네임은 10자 이하로 입력해주세요.');
        return false;
    } else {
        setNicknameError('');
        return true;
    }
};

4. 유효성 검사를 통과한 후 연필 버튼을 클릭하면 닉네임값을 저장한다.

const result = nicknameCheckHandler(nickname, setNicknameError);
  if (result) { // 유효성 검사를 통과하면
    setNickname(nickname); // 로컬에 닉네임을 세팅하고
    setIsEditing(false); // edit모드를 종료한다.
  }

문제와 시도

문제1.

연필 버튼 외부 영역을 클릭해도 로직이 정상적으로 실행되지 않는다.

시도1.

페이지 전체에 click 이벤트 리스너를 추가한다.

useEffect(() => {
  const handleClickOutside = (event) => {
    if (!event.target.closest('.nicknameInput') && !event.target.closest('.pencilButton')) {
      setNickname('');
      setNicknameError('');
      setIsEditing(false);
    }
  };

  window.addEventListener('click', handleClickOutside);

  return () => {
    window.removeEventListener('click', handleClickOutside);
  };
}, []);

문제2.

외부 영역 클릭 시에 로직이 잘 실행되지만, 유효성 검사 통과 후 세팅된 닉네임도 초기화 처리가 되어버린다.

시도2.

if (!e.target.closest('.nicknameInput') && !e.target.closest('.pencilButton')) {}
해당 조건문 삭제

const blurHandler = () => {
    setNickname('');
    setNicknameError('');
    setIsEditing(false);
  };

문제3.

닉네임 입력 후 연필 버튼을 눌러도 닉네임 값이 저장되지 않는다.
-> 문제의 원인은 onBlur와 onClick 이벤트가 거의 동시에 발생하기 때문. 연필 버튼을 클릭하면 input에서 벗어나므로 onBlur 이벤트가 발생하고, 이어서 onClick 이벤트가 발생한다. 이렇게 되면 blurHandler가 먼저 호출되어 edit모드가 종료되고 이후에 nicknameSubmitHandler가 호출되기 때문에 닉네임 세팅이 정상적으로 되지 않는다.

시도3.

연필 버튼에 onMouseDown 이벤트를 사용하여 nicknameSubmitHandler를 호출한다. mousedown 이벤트는 blur보다 먼저 발생하므로 문제를 해결할 수 있다.

문제4.

mousedown 이벤트는 마우스 기반의 이벤트로 모바일에서는 사용이 불가능하다. (내가 지금 진행하고 있는 프로젝트는 모바일 기반이다.)

해결!

모바일 환경에서 작동하는 onTouchStart 이벤트를 사용하여 해결했다! onTouchStart 이벤트는 onMouseDown 이벤트와 유사한 역할을 한다.

<ImageButton onTouchStart={nicknameSubmitHandler}>
	<img
		className='pencilButton'
		src={`${process.env.PUBLIC_URL}assets/svgs/pencil.svg`}
		alt='닉네임 바꾸기'
	/>
</ImageButton>

처음에 의도한대로 모든 기능이 잘 동작한다! 다만, 터치이벤트와 마우스이벤트를 모두 고려하여 만들고 싶은데 이에 관한 건 좀 더 알아봐야겠다.

profile
Markup Developer🧑‍💻

0개의 댓글