개발/Project(React-다이어리)

React - 사용자 입력 처리 / DOM 조작(useRef) / API 호출

잇(IT) 2023. 9. 18. 13:40
728x90


* 아래 코드를 확인하기 전 알아야 할 것이 있다. React는 같은 레벨에서 데이터를 주고 받을 수 없기 때문에 부모 컴포넌트로 데이터를 전달하고, 부모 데이터로부터 데이터를 전달 받아야 한다.

- 위와 같이 Event를 통해 부모 컴포넌트에서 자식 컴포너트로부터 데이터를 전달받고, 자식 컴포넌트로 데이터를 전달하는 것을 확인 할 수 있다.


- 사용자 입력, 입력 화면

- App.js

import { useEffect, useRef, useState } from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
import Lifecycle from "./Lifecycle";

function App() {
  const [data, setData] = useState([]);

  const dataId = useRef(0);

  //API 사용
  const getData = async () => {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/comments`
    ).then((res) => res.json());
    console.log(res);

    const initData = res.slice(0, 20).map((it) => {
      return {
        author: it.email,
        content: it.body,
        emotion: Math.floor(Math.random() * 5) + 1,
        created_date: new Date().getTime(),
        id: dataId.current++,
      };
    });

    setData(initData);
  };

  useEffect(() => {
    getData();
  }, []);

  const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current,
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };

  const onRemove = (targetId) => {
    console.log(`${targetId}가 삭제되었습니다.`);
    const newDiaryList = data.filter((it) => it.id !== targetId);
    setData(newDiaryList);
  };

  const onEdit = (targetId, newContent) => {
    setData(
      data.map((it) =>
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  };

  return (
    <div className="App">
      <Lifecycle />
      <DiaryEditor onCreate={onCreate} />
      <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
    </div>
  );
}

export default App;

 

 


- DiaryEditor 컴포넌트에 해당하는 부분이다.

 

- DiaryEditor.js

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

const DiaryEditor = ({ onCreate }) => {
// App.js로부터 onCreate Prop을 전달 받는다.

  const authorInput = useRef();
  const contentInput = useRef();

  const [state, setState] = useState({
    author: "",
    content: "",
    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.content.length < 5) {
      //focus
      contentInput.current.focus();
      return;
    }

    onCreate(state.author, state.content, state.emotion);
    alert("저장 성공");
    setState({
      author: "",
      content: "",
      emotion: "",
    });
  };

  return (
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
          ref={authorInput}
          name="author"
          value={state.author}
          onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea
          ref={contentInput}
          name="content"
          value={state.content}
          onChange={handleChangeState}
        />
      </div>
      <div>
        오늘의 감정 점수 :
        <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

function App() {
  const [data, setData] = useState([]);

  const dataId = useRef(0);

.......

const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current,
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };
  
  .......

- onCreate prop은 새로운 일기 하나를 새롭게 생성하는 prop이다.


const [state, setState] = useState({
    author: "",
    content: "",
    emotion: 1,
  });

- 최초의 화면에 useState()의 기본값에 의해 author="", content="", emotion=1로 값이 나타날 것이다.

 

return (
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
          ref={authorInput}
          name="author"
          value={state.author}
          onChange={handleChangeState}
        />
      </div>
      <div>
        <textarea
          ref={contentInput}
          name="content"
          value={state.content}
          onChange={handleChangeState}
        />
      </div>
      <div>
        오늘의 감정 점수 :
        <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>
  );
};

- 위 코드에 의해

- 위와 같이 최초의 화면이 나타난다.

 

- 값을 입력하는 태그인 input, textarea, select와 같이 입력 필드의 값이 변경되면 이벤트가 트리거 된다.

- 해당 event 객체를 전달하게 된다.

- target.name은 해당 HTML 요소의 name 속성의 값을 가져온다. target.value는 해당 HTML 요소의 value 속성의 값을 가져온다.

const handleChangeState = (e) => {
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  };

- handleChangeState의 함수는 이벤트 객체를 받아서 useState의 수정자인 setState를 이용하여 state의 값을 변경하고 리렌더링 한다.

- ...state는 스프레드를 통해 기존의 값은 유지한 채 새롭게 추가된 속성의 값을 이벤트 객체에 추가한다.

- 화면에 값을 입력하고 저장하기 버튼을 누르게 되면 handleSubmit 함수가 실행되게 된다.

const handleSubmit = () => {
    if (state.author.length < 1) {
      //focus
      authorInput.current.focus();
      return;
    }

    if (state.content.length < 5) {
      //focus
      contentInput.current.focus();
      return;
    }

    onCreate(state.author, state.content, state.emotion);
    alert("저장 성공");
    setState({
      author: "",
      content: "",
      emotion: "",
    });
  };

- if문을 통해 현재 state 객체에 저장된 값들을 조건에 맞게 검증하고 useRef를 통해 검증을 실패할 경우 해당 부분으로 focus()하게 지정한다. (useRef에 대해서는 아래에서 설명할 것이다.)

- 검증에 전부 통과하게 되며, onCreate() 함수가 호출되게 되고, 파라미터로 author, content, emotion이 전달되며, author, content, emotion 순서로 "", "", 1로 초기화 한다.

function App() {
  const [data, setData] = useState([]);

  const dataId = useRef(0);

.......

const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current,
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };
  
  .......

- App.js에서 전달 받은 onCreate 함수를 보게 되면, 전달받은 파라미터를 기반으로 newItem 객체를 생성하고, App.js의 useState인 setData를 통해 새롭게 추가된 Item 객체를 추가하면 값을 변경한다.


- useRef

- useRef는 React에서 사용되는 훅 중 하나로, 주로 DOM 요소를 선택하거나 다른 React 요소와의 상호 작용을 위해 사용된다.

1. DOM 요소 선택 : useRef를 사용하여 DOM 요소에 접근하고 조작할 수 있다. useRef를 사용하여 특정 DOM 요소를 선택하고 그 요소의 스크롤 위치를 변경하거나 포커스를 설정할 수 있다.

2. 컴포넌트의 상태 유지 : useRef를 사용하면 컴포넌트의 렌더링과 상관없이 값이 유지된다.  이것은 상태가 변경되어도 컴포넌트가 다시 렌더링되지 않고 값을 변경하고 유지하려는 경우에 유용하다.

 

- 즉, useRef는

1. 변수 관리

2. DOM 요소 선택의 용도

자주 사용된다.

 

- 바로 위에서 작성한 DiaryEditor.js 코드를 보게되면

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

const DiaryEditor = ({ onCreate }) => {
  const authorInput = useRef();
  const contentInput = useRef();
  
.......

  const handleSubmit = () => {
    if (state.author.length < 1) {
      //focus
      authorInput.current.focus();
      return;
    }

    if (state.content.length < 5) {
      //focus
      contentInput.current.focus();
      return;
    }

.......

  return (
    <div className="DiaryEditor">
      <h2>오늘의 일기</h2>
      <div>
        <input
          ref={authorInput}
          name="author"
          value={state.author}
          onChange={handleChangeState}
        />
      </div>
      
.......
      
      <div>
        <button onClick={handleSubmit}>일기 저장하기</button>
      </div>
    </div>
  );
};
export default DiaryEditor;

- const authorInput = useRef(); 코드가 작성된 것을 볼 수 있다. 

- 또 className="DiaryEditor"의 ref={authorInput}에 해당 변수가 사용된 것을 볼 수 있고, handleSubmit 컴포넌트에 author.current.focus()의 코드가 작성된 것을 볼 수 있다.

 

- 위의 코드는 handleSubmit 함수가 실행되고, if문에 true가 되어, authorInput.current.focus() 코드가 실행되면, authorInput은 useRef이기 때문에 해당 ref={authorInput}인 DOM으로 이동하고 해당 DOM에 focus()를 지정한다.

function App() {
  const [data, setData] = useState([]);

  const dataId = useRef(0);
  
  .......
  
  const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current,
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };

- 마찬가지로 App.js에도 useRef()가 사용되었는데, 위 코드 같은 경우에는 최초의 값을 0을 넣고, 계속해서 리렌더링 되더라도 해당 변수의 이전 값이 기억되도록 사용하는 용도로 사용되었다. 

- 기본적으로 값이 변경되고 리렌더링 되게 되면, 기존에 변수에 특정 값이 할당 되어 있다면 해당 값으로 돌아가거나 다른 외부에서 주입된 값이 들어 갈 수 있다.

- 하지만, useRef()를 사용하면 이전의 데이터가 리렌더링 되더라도 유지될 수 있다.


- API

https://jsonplaceholder.typicode.com/

 

JSONPlaceholder - Free Fake REST API

{JSON} Placeholder Free fake API for testing and prototyping. Powered by JSON Server + LowDB. Tested with XV. Serving ~2 billion requests each month.

jsonplaceholder.typicode.com

- JSON 테스트 데이터를 가져오기 위해 위 사이트를 통해 가져오도록 한다.

- 위와 같이 해당 url을 통해 json 데이터를 가져올 수 있다.

 

- App.js

.......

//API 사용
  const getData = async () => {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/comments`
    ).then((res) => res.json());
    console.log(res);

    const initData = res.slice(0, 20).map((it) => {
      return {
        author: it.email,
        content: it.body,
        emotion: Math.floor(Math.random() * 5) + 1,
        created_date: new Date().getTime(),
        id: dataId.current++,
      };
    });
    
    setData(initData);
    
    
    .......

- fetch()는 첫번째 인자로 url을 받고,  반환값으로 Promise 객체를 반환한다

- 응답에 성공했을 경우 fetch().then을 통해 응답에 대한 콜백 함수를 실행 할 수 있다. 해당 url을 통해 데이터를 받을 때 문자열 형태로 받기 때문에 .json()을 통해 해당 값을 json 형태로 변환한다.

- 결과적으로 res 변수에는 json 형태 즉, 객체가 담기게 된다.

- initData를 통해 객체를 20개만 가져오고, map을 통해 받아온 객체의 속성을 현재 개발중인 객체에 맞게 객체 형태를 만들어서 반환한다.

 

- 결과적으로 map을 통해 새롭게 생성된 20개의 객체가 setData를 통해 data 변수에 입력되고 리렌더링 된다.

728x90