Tech/ReactJS

[리액트/React] 새로 추가한 input에 focus 주기

닝닝깅 2023. 12. 15. 15:07

📌 구현화면

 

버튼을 누르면 새로운 input이 생기고,

UX를 위하여 새로 생긴 input 창에 바로 focus 되는 방식으로 동작하기를 원했다.

 

📌 구현내용

1. input Ref 만들기

 

questions은 나열하는 질문들의 값을 담는 state이고, lastInputRef는 가장 마지막의 input 값을 담기 위한 ref이다.

 

map 함수를 사용하여 questions를 컴포넌트의 인자에 담아 렌더링 시킨다. 이때 lastInputRef도 인자로 담아 보낸다.

이렇게 하면 모든 질문의 input의 값을 거쳐서 결국 마지막에 렌더링 되는 마지막 input의 값이 lastInputRef에 담기게 된다.

 

 

const [questions, setQuestions] = useState();
const lastInputRef = useRef(null);

const handleQuestionAdd = () => {
  setQuestions((prev) => [...prev, { question: "" }]);
};

return (
  <>
    {questions.map((item, idx) => (
      <ManagementQuestion
        key={idx}
        question={item.question}
        onClick={() => handleQuestionDelete(idx)}
        lastInputRef={lastInputRef}
      />
    ))}
    <AddQuestionButton onClick={handleQuestionAdd}>
      질문 추가하기
    </AddQuestionButton>
  </>
);

 

 

2. state의 길이가 달라질 때마다 ref 값을 받아 focus 주기

 

위와 같은 상태에서 추가 버튼을 누르면 questioins에 새로운 값이 추가되고 ref의 현재 객체 값은 새로 추가된 값의 input로 변경될 것이다.

따라서 useEffect 훅을 사용하여 questions의 길이가 달라질 때마다 ref 객체의 현재 값을 받아 focus를 준다.

useEffect(() => {
  lastInputRef.current.focus();
}, [questions.length]);

 

 

 

3. 질문 삭제 시 발생하는 동일 이벤트 발생 방지

 

해당 로직을 만들면 삭제할 때도 questions의 변경되기 때문에 동일한 이벤트가 발생한다.

이를 방지하기 위해서 isFocus라는 값을 useRef를 생성하였다.

이 값은 현재 추가 이벤트가 발생한 것인지를 true/false 값으로 구분하기 위해 사용된다.

 

이때 useState 대신 useRef를 사용한 이유는
useState는 값이 바뀔 때마다 리렌더링 이벤트가 발생하지만, useRef는 값이 바뀌어도 리렌더링이 발생하지 않기 때문이다.
이 값은 내부 로직에서만 필요하고 렌더링과는 연관이 없는 값이기 때문에 useRef를 사용하였다.

 

질문 추가 함수에서 isFocus의 값을 true로 변경하고,

isFocus가 true일 때만 inputRef에 focus를 준 뒤,

isFocus의 값을 다시 false로 변경한다.

 

const isFocus = useRef(false);

const handleQuestionAdd = () => {
  isFocus.current = true;
  setQuestions((prev) => [...prev, { question: "" }]);
};

useEffect(() => {
  const isRemoveInput = !lastInputRef.current || !isFocus.current;

  if (isRemoveInput) return;

  lastInputRef.current.focus();
  isFocus.current = false;
}, [questions.length, isFocus]);

 

전체코드

const [questions, setQuestions] = useState();
const lastInputRef = useRef(null);
const focus = useRef(false);

const handleQuestionDelete = (removeIdx) => {
  setQuestions(questions.filter((question, idx) => idx != removeIdx));
};

const handleQuestionAdd = () => {
  focus.current = true;
  setQuestions((prev) => [...prev, { question: "" }]);
};

useEffect(() => {
  const isRemoveInput = !lastInputRef.current || !focus.current;

  if (isRemoveInput) return;

  lastInputRef.current.focus();
  focus.current = false;
}, [questions.length, focus]);

return (
  <>
    {questions.map((item, idx) => (
      <ManagementQuestion
        key={idx}
        question={item.question}
        onClick={() => handleQuestionDelete(idx)}
        setQuestions={setQuestions}
        lastInputRef={lastInputRef}
      />
    ))}
    <AddQuestionButton onClick={handleQuestionAdd}>
      질문 추가하기
    </AddQuestionButton>
  </>
);

 

 

📌 더 생각해볼 점

이 방법은 기능상의 문제는 없지만 모든 input에 ref를 단다는 점에서 비교적 효율성이 떨어지는 방법이라고 생각한다. questions의 인덱스 값을 사용하여 추가되는 마지막 question의 input에만 useRef를 달 수 있는 방법이 있다면 좋을 것 같다는 생각을 했다. 이에 대한 구체적인 방식은 더 생각해봐야겠다.

 


참고

https://stackoverflow.com/questions/59209612/react-how-to-set-focus-on-last-input-after-adding-it