useRef 여러개 관리하기

유나랑·2024년 8월 8일

코딩온 X SeSAC

목록 보기
24/30
post-thumbnail

useRef hook를 배웠다. 과제를 하던중 여러 input창에 모두 useRef를 이용하여 같은 기능을 구현하고싶은데 각각 따로따로 써야하나 의문이 들고 찾아본 결과를 정리해보겠다!

useRef란?

렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook이다. 따라서 용도는 1. 정보 저장 2. DOM 조작 이다.

구현할 기능

  • input에 값이 입력되지 않으면 input창에 focus주기
  • input/textarea에서 조건에 충족하지 못하면 alert 띄우는 것은 UX 측면에서 좋지않다.
  • 트렌디한 사이트는 alert을 사용하지않고 input에 focus 주는 방식을 사용
  • input창 3개에 적용할 것.

useRef 각각 사용하기

  1. useRef 생성
const inputWriterRef = useRef();
const inputTitleRef = useRef();
const inputSearchRef = useRef();
  1. 직접 접근하고 싶은 DOM 요소에 ref props 설정
<input ref = {inputWriterRef}/>;
<input ref = {inputTitleRef}/>;
<input ref = {inputSearchRef}/>;
  1. useRef()를 이용해서 만든 객체의 current값에 focus() DOM API 사용
inputWriterRef.current.focus();
inputTitleRef.current.focus();
inputSearchRef.current.focus();

📌 useRef가 너무 많아지면 어떻게 사용하지?

방법이 2가지가 있다.
1. 객체 사용
: 명확한 이름으로 참조를 관리해야 하거나, 각 참조가 특정한 의미를 갖는 경우 유리
2. 배열 사용
: 동일한 유형의 여러 참조를 관리하거나, 참조가 순차적으로 추가되는 경우 유리(반복적인 UI 요소)

이번 상황에서는 명확한 이름으로 관리하는게 적절하다 생각되어 객체를 사용하였다.

  1. useRef 객체 생성
const refs = useRef({
    writer: null,
    title: null,
    searchText: null,
});
  1. 직접 접근하고 싶은 DOM 요소에 ref props 설정
ref={(el) => (refs.current.writer = el)}
  • ref에 전달되는 콜백함수이다.
  • input 요소에 대한 DOM을 참조
  • refs.current.writer.focus()를 호출하여 포커스을 줌.
  1. useRef()를 이용해서 만든 객체의 current 값에 focus() DOM API 사용
if (writer.trim().length === 0) {
      refs.current.writer.focus();
      return;
    }

전체 코드

import React, { useRef, useState } from "react";

function Practice() {
  const [text, setText] = useState([]);
  const [writer, setWriter] = useState("");
  const [title, setTitle] = useState("");
  const [searchOption, setSearchOption] = useState("writer");
  const [searchText, setSearchText] = useState("");
  const [filteredText, setFilteredText] = useState([]);

  // 객체를 사용하여 useRef 생성
  const refs = useRef({
    writer: null,
    title: null,
    searchText: null,
  });

  // 데이터 추가
  const addText = (e) => {
    e.preventDefault();
    const newText = text.concat({
      id: text.length + 1,
      writer: writer,
      title: title,
    });
    if (writer.trim().length === 0) {
      refs.current.writer.focus();
      return;
    }
    if (title.trim().length === 0) {
      refs.current.title.focus();
      return;
    }
    setText(newText);
    setWriter("");
    setTitle("");
  };

  // 검색 기능
  const handleSearch = () => {
    if (searchText.trim().length === 0) {
      //   setFilteredText([]);
      refs.current.searchText.focus();
      return;
    }

    const filtered = text.filter((info) => {
      if (searchOption === "writer") {
        return info.writer.includes(searchText);
      } else if (searchOption === "title") {
        return info.title.includes(searchText);
      }
      return false;
    });
    setFilteredText(filtered);
    setSearchText("");
  };

  // 전체 검색
  const handleSearchAll = () => {
    setFilteredText(text);
  };

  // 키보드 이벤트
  const handleKeyDown = (e, action) => {
    if (e.keyCode === 13) {
      action(e);
    }
  };

  return (
    <>
      <form style={{ margin: "10px" }}>
        작성자:
        <input
          type="text"
          placeholder="작성자"
          value={writer}
          onChange={(e) => setWriter(e.target.value)}
          ref={(el) => (refs.current.writer = el)}
        />
        제목:
        <input
          type="text"
          placeholder="제목"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          onKeyDown={(e) => handleKeyDown(e, addText)}
          ref={(el) => (refs.current.title = el)}
        />
        <button onClick={addText}>작성</button> <br />
      </form>
      <div style={{ margin: "10px" }}>
        <select
          value={searchOption}
          onChange={(e) => setSearchOption(e.target.value)}
        >
          <option value="writer">작성자</option>
          <option value="title">제목</option>
        </select>
        <input
          type="text"
          placeholder="검색어를 입력하세요."
          value={searchText}
          onChange={(e) => setSearchText(e.target.value)}
          onKeyDown={(e) => handleKeyDown(e, handleSearch)}
          ref={(el) => (refs.current.searchText = el)}
        />
        <button onClick={handleSearch}>검색</button>
        <button onClick={handleSearchAll}>전체</button>
      </div>
      <table
        border="1"
        cellPadding="10"
        cellSpacing="1"
        style={{ margin: "10px" }}
      >
        <thead>
          <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성자</th>
          </tr>
        </thead>
        <tbody>
          {text.map((value) => (
            <tr key={value.id}>
              <td>{value.id}</td>
              <td>{value.title}</td>
              <td>{value.writer}</td>
            </tr>
          ))}
        </tbody>
      </table>
      {filteredText.length > 0 ? (
        <p>검색 결과</p>
      ) : (
        <p>검색 결과가 없습니다.</p>
      )}
      {filteredText.length > 0 && (
        <table
          border="1"
          cellPadding="10"
          cellSpacing="1"
          style={{ margin: "10px" }}
        >
          <thead>
            <tr>
              <th>번호</th>
              <th>제목</th>
              <th>작성자</th>
            </tr>
          </thead>
          <tbody>
            {filteredText.map((value) => (
              <tr key={value.id}>
                <td>{value.id}</td>
                <td>{value.title}</td>
                <td>{value.writer}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </>
  );
}

export default Practice;

마무리하며..

리액트의 useRef hook에 대해 학습하였다. 용도를 알게 되었고 어떻게 하면 더 효율적으로 쓸 수 있을까 고민하는 시간이 되었다. 리액트에는 많은 hook이 존재한다고 하는데 차근히 배워보고 익혀야겠다.

출처 및 참고
1. [새싹 X 코딩온] 영등포 캠퍼스 6기 입문자도 가능한 웹 개발자 부트캠프 강의 교안
2. https://ko.react.dev/reference/react/useRef
3. https://velog.io/@minho100227/useRef%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-focus%EA%B8%B0%EB%8A%A5

0개의 댓글