밍비
프로그램의 편린
밍비
전체 방문자
오늘
어제
  • 분류 전체보기 (64)
    • Spring (2)
    • TIL (23)
    • 프로그래머스 (12)
    • Udemy (16)
    • Typescript (2)
    • MERN (1)
    • AWS (7)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 리스트 조회
  • API 호출
  • state 관리
  • 리액트 생애주기
  • 리액트 프로젝트 만들기
  • DOM
  • Page Moving
  • overflow-wrap
  • react
  • 한입 크기로 잘라먹는 리액트
  • 네이버커넥트
  • 함수형 update
  • 수평 스케일링
  • state 끌어올리기
  • 컴포넌트트리
  • 리액트
  • useState
  • AWS Regions
  • Points of Presence
  • useRef
  • 한입크기로잘라먹는리액트
  • Availability Zones
  • useParams
  • 데이터 수정
  • Edge Locations
  • 서비스아키텍처
  • 분산저장소
  • State 합치기
  • 리액트 reducer
  • useNavigate

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
밍비

프로그램의 편린

[React] React에서 DOM 조작, 데이터 조회 기능 (리스트 렌더링)
Udemy

[React] React에서 DOM 조작, 데이터 조회 기능 (리스트 렌더링)

2023. 4. 15. 20:47
728x90

이 블로그는 "한입 크기로 잘라먹는 리액트" 강의를 보고 복습하고자 작성되었습니다!

목차

1. useRef로 DOM 조작하기

2. 일기 리스트 렌더링(조회기능)

3. 컴포넌트 분할

 

1. useRef로 DOM 조작하기

DOM(Document Object Model)이란 문서 객체 모델이라는 뜻으로, 웹 페이지의 요소들을 구조화시켜 접근, 조작할 수 있도록 API를 제공해주는 인터페이스입니다.

 

const handleSubmit = () => {
  if (state.author.length < 1) {
    alert('작성자는 최소 1글자 이상 입력해주세요.');
    return;
  }
  if (state.contents.length < 5) {
    alert('일기 본문은 최소 5글자 이상 입력해주세요.');
    return;
  }
  alert('저장 성공');
};

이 코드는 alert를 사용해 form 요소들의 글자수를 체크하는 코드입니다.

다이어리 폼을 제출할 때 칸이 비워져 있으면 간단하게 alert를 띄울 수도 있으나,

alert는 좋은 UX가 아닙니다.

이렇게 뜨는 경고창이 alert입니다.

alert처럼 단순 텍스트가 아니라, 실시간으로 화면에서 보여주는 것이 사용자에게 더 잘 보이기 때문입니다.

가리키고 싶은 특정 DOM 요소에다 focus를 주어야 하는데, 이때 사용할 수 있는 게 useRef입니다.

 

그럼 저번에 작업하던 다이어리 프로그램에서 useRef 기능을 이용해,

author나 contents를 안 썼을 때 해당 DOM(textInput, textarea)에 focus를 줍시다.

 

1. useRef 임포트하기

import { useRef, useState } from 'react';

2. textInput에 focus 주기

2-1. useRef() 함수 호출, authorInput에 넣기

const authorInput = useRef();

그럼 authorInput에 React.MutableRefObject 자료형이 들어갑니다.

MutableRefObject는 html, 즉 DOM 요소에 접근하는 기능을 합니다.

2-2. return 속 input 태그에 속성으로 ref={ authorInput } 넣기

ref={authorInput}

2-3. 위에서 handleSubmit 함수에서 author 부분 alert를 지우고, 아래 코드 넣기

authorInput.current.focus();

현재 가리키는 authorInput 부분에 focus를 넣는다는 뜻입니다.

 

3. 같은 방법으로 textArea에 focus 주기

  const handleSubmit = () => {
    if (state.author.length < 1) {
      // focus
      authorInput.current.focus();
      return;
    }
    if (state.contents.length < 5) {
      // focus
      contentsInput.current.focus();
      return;
    }
    alert('저장 성공');
  };

handleSubmit 함수의 전체 코드입니다.

<div>
  <input
    ref={authorInput}
    name='author'
    value={state.author}
    onChange={handleChangeState}
    placeholder='작성자'
    type='text'
  />
</div>
<div>
  <textarea
    ref={contentsInput}
    name='contents'
    value={state.contents}
    onChange={handleChangeState}
    placeholder='일기'
    type='text'
  />
</div>

return에서 author과 contents 입력 부분입니다.

 

2. 일기 리스트 렌더링 (조회)

일기 작성 부분 밑에 내가 작성한 일기 리스트를 올려봅시다.

 

1. DiaryList.js 파일 생성, 초기설정

 

DiaryList.js

const DiaryList = () => {
  return (
    <div>
      <h2>일기 리스트</h2>
    </div>
  );
};
 
export default DiaryList;

이렇게 작성한 후, App.js에서 임포트하고 return 부분에 컴포넌트 태그를 추가합니다.

import DiaryList from './DiaryList';
return (
  <div className='App'>
    <DiaryEditor />
    <DiaryList diaryList={dummyList} />
  </div>
);

 

2. App.js에 dummy list 생성 후 태그로 전달

const dummyList = [
  {
    id: 1,
    author: '최명빈',
    contents: "Show it to the world we blossomin' some more",
    emotion: 5,
    created_date: new Date().getTime(),
  },
  ]

이런 더미 데이터를 여러 개 만들어 dummyList 리스트로 만든 후,

DiaryList 컴포넌트에 태그로 전달합니다.

<DiaryList diaryList={dummyList} />

DiaryList 컴포넌트에서는 prop으로 dummyList를 받아올 수 있습니다.

const DiaryList = ({ diaryList }) => {
  return (
    <div>
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일가 있습니다.</h4>
      <div>
        {diaryList.map((it) => (
          <div>일기 아이템</div>
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

diaryList에 빈 리스트를 받았을 경우를 대비해, defaultProps도 설정해줍니다.

 

하지만, 이렇게 쓰면 브라우저 콘솔에 경고가 뜹니다.

Each child in a list should have a unique "key" prop.

이 문제는 일기 리스트에서 최상위 div에 id를 붙여 해결이 가능합니다.

{
  diaryList.map((it) => (
    <div key={it.id}>
      <div>작성자: {it.author}</div>
      <div>내용: {it.contents}</div>
      <div>감정: {it.emotion}</div>
      <div>작성시간: {it.created_date}</div>
    </div>
  ));
}

map 안에 쓰인 화살표함수 반환값의 최상위 div에서 key로 it.id를 각각 매기도록 작성했습니다.

 

만약 id가 없는 객체 리스트일 경우, map에서 it와 함께 idx를 함께 전달하면, 각 항목에 자동으로 idx가 붙습니다.

{diaryList.map((it, idx) => (

하지만 idx를 쓸 경우, 데이터가 수정되면 idx가 바뀌어 리액트에서 문제가 발생할 수 있습니다.

이 이유로 여기서는 id만 쓰도록 하겠습니다.

 

3. 컴포넌트 분할

DiaryList에 수정, 삭제 기능까지 넣으면, 이 컴포넌트의 return이 너무 커져서 코드가 길어지고 에러가 발생하기 쉽습니다.

그래서 각 Diary 아이템을 별도의 컴포넌트로 분할할 것입니다.

 

1. DiaryItem.js 생성

const DiaryItem = () => {
  return <div className='DiaryItem'></div>;
};
export default DiaryItem;

이제 컴포넌트를 만드는 게 점점 익숙해지고 있습니다.

 

2. DiaryList의 리턴에는 diaryList 항목을 매핑하고, key를 넣어, DiaryItem 컴포넌트로 전달

return (
  <div>
    <h2>일기 리스트</h2>
    <h4>{diaryList.length}개의 일가 있습니다.</h4>
    <div>
      {diaryList.map((it) => (
        <DiaryItem key={it.id} {...it} />
      ))}
    </div>
  </div>
);

이렇게 DiaryItem 컴포넌트를 불러오면 해당 컴포넌트가 자동으로 임포트됩니다.

 

3. DiaryItems 컴포넌트에 들어가 props로 받아옴

const DiaryItem = ({ id, author, contents, emotion, created_date }) => {
};

더미 데이터마다 속성들을 객체로 불러옵니다.

4. 렌더링

return (
  <div className='DiaryItem'>
    <div className='info'>
      <span>
        작성자: {author} | 감정 점수: {emotion}
      </span>
      <span className='date'>{new Date(created_date).toLocaleString()}</span>
    </div>
    <div className='contents'>{contents}</div>
  </div>
);

불러온 데이터를 화면에 띄웁니다.

여기서 created_date는 ms단위라서

Date 객체로 새로 만든 뒤, toLocaleString 함수를 먹여서 한글로 시간이 나오게 고쳤습니다.

 

 

참고) 전체 코드

DiaryList.js

import DiaryItem from './DiaryItem';

const DiaryList = ({ diaryList }) => {
  return (
    <div>
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={it.id} {...it} />
        ))}
      </div>
    </div>
  );
};
DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

 

DiaryItem.js

const DiaryItem = ({ id, author, contents, emotion, created_date }) => {
  return (
    <div className='DiaryItem'>
      <div className='info'>
        <span>
          작성자: {author} | 감정 점수: {emotion}
        </span>
        <span className='date'>{new Date(created_date).toLocaleString()}</span>
      </div>

      <div className='contents'>{contents}</div>
    </div>
  );
};
export default DiaryItem;

 

 

DiaryEditor.js

import { useRef, useState } from 'react';

const DiaryEditor = () => {
  const authorInput = useRef();
  const contentsInput = useRef();
  const [state, setState] = useState({
    author: '',
    contents: '',
    emotion: 1,
  });

  const handleChangeState = (e) => {
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  };
  const handleSubmit = () => {
    if (state.author.length < 1) {
      // focus
      authorInput.current.focus();
      return;
    }
    if (state.contents.length < 5) {
      // focus
      contentsInput.current.focus();
      return;
    }
    alert('저장 성공');
  };
  return (
    <div className='DiaryEditor'>
      <h2>오늘의 일기</h2>
      <div>
        <input
          ref={authorInput}
          name='author'
          value={state.author}
          onChange={handleChangeState}
          placeholder='작성자'
          type='text'
        />
      </div>
      <div>
        <textarea
          ref={contentsInput}
          name='contents'
          value={state.contents}
          onChange={handleChangeState}
          placeholder='일기'
          type='text'
        />
      </div>
      <div>
        <span>오늘의 감정점수: </span>
        <select
          name='emotion'
          value={state.emotion}
          onChange={handleChangeState}>
          <option value={1}>1</option>
          <option value={2}>2</option>
          <option value={3}>3</option>
          <option value={4}>4</option>
          <option value={5}>5</option>
        </select>
      </div>
      <div>
        <button onClick={handleSubmit}>일기 저장</button>
      </div>
    </div>
  );
};
export default DiaryEditor;

 

App.js

import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
const dummyList = [
  {
    id: 1,
    author: '밍비',
    contents: "Show it to the world we blossomin' some more",
    emotion: 5,
    created_date: new Date().getTime(),
  },
  {
    id: 2,
    author: '밍비',
    contents: "Show it to the world we blossomin' some more",
    emotion: 5,
    created_date: new Date().getTime(),
  },
  {
    id: 3,
    author: '밍비',
    contents: "Show it to the world we blossomin' some more",
    emotion: 5,
    created_date: new Date().getTime(),
  },
  {
    id: 4,
    author: '밍비',
    contents: "Show it to the world we blossomin' some more",
    emotion: 5,
    created_date: new Date().getTime(),
  },
];
function App() {
  return (
    <div className='App'>
      <DiaryEditor />
      <DiaryList diaryList={dummyList} />
    </div>
  );
}

export default App;

 

App.css

.DiaryEditor {
  border: 1px solid gray;
  text-align: center;
  padding: 20px;
}

.DiaryEditor input,
textarea {
  margin-bottom: 20px;
  width: 500px;
  padding: 10px;
}

.DiaryEditor textarea {
  height: 150px;
}

.DiaryEditor select {
  width: 300px;
  padding: 10px;
  margin-bottom: 20px;
}

.DiaryEditor button {
  width: 500px;
  padding: 10px;
  cursor: pointer;
}

/* List */

.DiaryList {
  border: 1px solid gray;
  padding: 20px;
  margin-top: 20px;
}

.DiaryList h2 {
  text-align: center;
}

/* Item */
.DiaryItem {
  background-color: rgb(240, 240, 240);
  margin-bottom: 10px;
  padding: 20px;
}
.DiaryItem .info {
  border-bottom: 1px solid gray;
  padding-bottom: 10px;
  margin-bottom: 10px;
}
.DiaryItem .date {
  color: gray;
}

.DiaryItem .contents {
  font-weight: bold;
  margin-bottom: 30px;
  margin-top: 30px;
}

 

 

728x90

'Udemy' 카테고리의 다른 글

[React] 데이터 삭제하기, 데이터 수정하기  (0) 2023.04.17
[React] 데이터 추가 기능  (0) 2023.04.16
[React] 리액트로 프로젝트 만들기  (0) 2023.04.15
[React] React 입문  (0) 2023.04.09
[Node.js] Node.js 기초, 실행환경 구성  (0) 2023.04.09
    밍비
    밍비

    티스토리툴바