본문 바로가기
부트캠프 개발일지 2023-2024/React 리액트

[10주차] 리액트심화: React Query 실습 (초기세팅, 데이터가져오기, 데이터입력하기)

by whereanna00 2023. 12. 7.

이론과 개념도 함께 참고!

 

[10주차] 리액트심화: React Query 이론과 개념

React Query 1 react query 란, 서버 상태 관리를 쉽게 할 수 있도록 도와주는 라이브러리이다. 서버상태(server state)는 서버에 요청하고 응답받는 모든 과정과 연관된 데이터들을 의미한다. 서버상태관

whereannalee.tistory.com


1. 초기세팅 & 데이터 가져오기

1. 전체를 아우르는 부모 컴포넌트에 queryClient 정의하기 -> return문에서 QueryClientProvider 로 감싸주기

더보기

App.jsx

import React from "react";
import Router from "./shared/Router";

const App = () => {
  return <Router />;
};

export default App;

 

=>

 

import React from "react";
import Router from "./shared/Router";
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

const App = () => {


  return (
  <QueryClientProvider client={queryClient}>
      <Router />
  </QueryClientProvider>
  );
};

export default App;

 

 

2. 서버에 요청하는 파일들을 하나로 모아놓는 src>api 파일 만들기

 

3. src>api>todos.js 파일 만들기

 

4. todos.js 파일에 axios 를 import 하기 -> 조회 함수 만들기 (getTodos)

// axios 요청이 들어가는 모든 모듈

import axios from "axios";

// 조회
const getTodos = async () => {
  const response = await axios.get("");
};

 

 

5. root>.env 파일 만들기 -> 환경변수넣기

**항상 REACT_APP_ 으로 시작해야함

REACT_APP_SERVER_URL=http://localhost:4xxx

 

 

 

6. 다시 api>todos.js 로 돌아와서 조회함수의 url 자리에 백틱으로 감싼 환경변수를 넣어준다 -> 뒤에 /todos 추가 -> 앞에 process.env. 추가 => return 추가

const response = await axios.get(`${REACT_APP_SERVER_URL}/todos`);

 

// axios 요청이 들어가는 모든 모듈

import axios from "axios";

// 조회
const getTodos = async () => {
  const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
  return response;
};

 

 

 

7. db 로부터 데이터가 잘 들어와지는 지 확인하기

- console.log 추가하기

- 조회함수를 export 하기 추가

 

api>todos.js

// axios 요청이 들어가는 모든 모듈

import axios from "axios";

// 조회
const getTodos = async () => {
  const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
  console.log("조회함수결과 response", response);
  return response;
};

export { getTodos };

 

 

 

7-1. db 로부터 데이터가 잘 들어와지는 지 확인하기

- App.jsx>Router.js>Main.jsx>TodoList.jsx 로 들어오기

- getTodos import 하기

import { getTodos } from "../../../api/todos";

 

 

- 컴포넌트 안 자바스크립트 공간에, 리액트 쿼리를 불러와 UI에 뿌려주기

- 기존 리덕스로 가져와쓰던 todos return 부분은 주석처리

const 구조분해할당으로 { isLoading, isError, data } = useQuery("쿼리의 이름", 조회를 해오는 비동기 함수);

 

이전 코드

더보기
import React from "react";
import { useSelector } from "react-redux";
import { StyledDiv, StyledTodoListHeader, StyledTodoListBox } from "./styles";
import Todo from "../Todo";

function TodoList({ isActive }) {
  const todos = useSelector((state) => state.todos);

  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {todos
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })}
      </StyledTodoListBox>
    </StyledDiv>
  );
}

export default TodoList;

 

적용 후의 코드

import React from "react";
import { useSelector } from "react-redux";
import { StyledDiv, StyledTodoListHeader, StyledTodoListBox } from "./styles";
import Todo from "../Todo";
import { getTodos } from "../../../api/todos";
import { useQuery } from "react-query";

/**
 * 컴포넌트 개요 : 메인 > TODOLIST. 할 일의 목록을 가지고 있는 컴포넌트
 * 2022.12.16 : 최초 작성
 *
 * @returns TodoList 컴포넌트
 */
function TodoList({ isActive }) {
  // const todos = useSelector((state) => state.todos); // 리덕스에서 불러와 뿌리는 방법이므로 주석처리함

  //리액트 쿼리방법
  const { isLoading, isError, data } =useQuery("todos", getTodos);

  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {/* {todos
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })} */}
      </StyledTodoListBox>
    </StyledDiv>
  );
}

export default TodoList;

 

 

- 서버 재시동

yarn json-server —watch db.json —port 4000

 

 

- 데이터 들어오나 확인 (개발자도구)

 

 

 

 

8. api > todos.js 에서 response가 아닌 response.data를 반환하기

왜? 개발자도구 콘솔에서도 봤다시피, 우리가 원하는 데이터는 response라는 객체 안에 data라는 key의 value 값들이기때문.

 

api>todos.js

// axios 요청이 들어가는 모든 모듈

import axios from "axios";

// 조회
const getTodos = async () => {
  const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
  console.log("조회함수결과 response.data", response.data);
  return response.data;
};

export { getTodos };

 

콘솔에서 확인 완료

 

 

 

9. db로부터 가져온 데이터를 UI 에 뿌려준다.

 

이전코드에서 todos -> data 로 바꿔주기

  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {/* {todos
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })} */}
      </StyledTodoListBox>
    </StyledDiv>
  );

 

 

적용코드

  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {data
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })}
      </StyledTodoListBox>
    </StyledDiv>
  );

 


TIP

useQuery를 통해 react-query를 동작할 때의 아주 무서운 기능은,
useQuery가 아직 완료되지 않았을 때 isLoading이 자동으로 true가 된다.

 

  if(isLoading){
    return <h1>로딩중</h1>;
  }

 

를 추가하면, isLoading 에 대한 결과를 바로 화면에 뿌려준다.

 

isError도 쓸 수 있다.

확인하는 방법

(1) 경로에 오타주기 : 로딩중 -> 오류발생

(2) 서버끄고 새로고침: 로딩중 -> 오류발생

  if(isError){
    return <h1>오류발생</h1>;
  }

 

 

전체코드

더보기
import React from "react";
import { useSelector } from "react-redux";
import { StyledDiv, StyledTodoListHeader, StyledTodoListBox } from "./styles";
import Todo from "../Todo";
import { getTodos } from "../../../api/todos";
import { useQuery } from "react-query";

function TodoList({ isActive }) {
  // const todos = useSelector((state) => state.todos); // 리덕스에서 불러와 뿌리는 방법이므로 주석처리함

  //리액트 쿼리방법
  const { isLoading, isError, data } =useQuery("todos", getTodos);

  if(isLoading){
    return <h1>로딩중</h1>;
  }

  if(isError){
    return <h1>오류발생</h1>;
  }
  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {data
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })}
      </StyledTodoListBox>
    </StyledDiv>
  );
}

export default TodoList;

2. 데이터 입력하기(mutation)

 

1. src > api > todos.js 에 추가 함수 추가하기

// 추가
const addTodo = async (newTodo) => {
  await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
}

 

2. addTodo(추가함수) export 하기

export { getTodos, addTodo };

 

// axios 요청이 들어가는 모든 모듈

import axios from "axios";

// 조회
const getTodos = async () => {
  const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
  console.log("조회함수결과 response.data", response.data);
  return response.data;
};

// 추가
const addTodo = async (newTodo) => {
  await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
};

export { getTodos, addTodo };

 

 

 

3. App > Router > Main > Input 에 들어가기

 

4. Input 컴포넌트에서 제출버튼과 연결된 함수 찾기 (handleSubmitButtonClick)

const handleSubmitButtonClick = (event) => {
    // submit의 고유 기능인, 새로고침(refresh)을 막아주는 역함
    event.preventDefault();

    // 제목과 내용이 모두 존재해야만 정상처리(하나라도 없는 경우 오류 발생)
    // "01" : 필수 입력값 검증 실패 안내
    if (!title || !contents) {
      return getErrorMsg("01", { title, contents });
    }

    // 이미 존재하는 todo 항목이면 오류
    const validationArr = todos.filter(
      (item) => item.title === title && item.contents === contents
    );

    // "02" : 내용 중복 안내
    if (validationArr.length > 0) {
      return getErrorMsg("02", { title, contents });
    }

    // 추가하려는 todo를 newTodo라는 객체로 세로 만듦
    const newTodo = {
      title,
      contents,
      isDone: false,
      id: uuidv4(),
    };

    // todo를 추가하는 reducer 호출
    // 인자 : payload
    dispatch(addTodo(newTodo));

    // state 두 개를 초기화
    setTitle("");
    setContents("");
  };

 

이전코드

더보기
import React, { useState } from "react";
import LabledInput from "../common/LabledInput";
import HeightBox from "../common/HeightBox";
import { StyledButton } from "./styles";
import { FlexDiv } from "./styles";
import RightMarginBox from "../common/RightMarginBox";
import "./styles";
import { StyledDiv } from "./styles";
import { useDispatch, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import { addTodo } from "../../modules/todos";

/**
 * 컴포넌트 개요 : Todo 메인 페이지에서 제목과 내용을 입력하는 영역
 * 2022.12.16 : 최초 작성
 *
 * @returns Input 컴포넌트
 */
function Input() {
  const dispatch = useDispatch();

  // useSelector를 통한, store의 값 접근
  const todos = useSelector((state) => state.todos);

  // 컴포넌트 내부에서 사용할 state 2개(제목, 내용) 정의
  const [title, setTitle] = useState("");
  const [contents, setContents] = useState("");

  // 에러 메시지 발생 함수
  const getErrorMsg = (errorCode, params) => {
    switch (errorCode) {
      case "01":
        return alert(
          `[필수 입력 값 검증 실패 안내]\n\n제목과 내용은 모두 입력돼야 합니다. 입력값을 확인해주세요.\n입력된 값(제목 : '${params.title}', 내용 : '${params.contents}')`
        );
      case "02":
        return alert(
          `[내용 중복 안내]\n\n입력하신 제목('${params.title}')및 내용('${params.contents}')과 일치하는 TODO는 이미 TODO LIST에 등록되어 있습니다.\n기 등록한 TODO ITEM의 수정을 원하시면 해당 아이템의 [상세보기]-[수정]을 이용해주세요.`
        );
      default:
        return `시스템 내부 오류가 발생하였습니다. 고객센터로 연락주세요.`;
    }
  };

  // title의 변경을 감지하는 함수
  const handleTitleChange = (event) => {
    setTitle(event.target.value);
  };

  // contents의 변경을 감지하는 함수
  const handleContentsChange = (event) => {
    setContents(event.target.value);
  };

  // form 태그 내부에서의 submit이 실행된 경우 호출되는 함수
  const handleSubmitButtonClick = (event) => {
    // submit의 고유 기능인, 새로고침(refresh)을 막아주는 역함
    event.preventDefault();

    // 제목과 내용이 모두 존재해야만 정상처리(하나라도 없는 경우 오류 발생)
    // "01" : 필수 입력값 검증 실패 안내
    if (!title || !contents) {
      return getErrorMsg("01", { title, contents });
    }

    // 이미 존재하는 todo 항목이면 오류
    const validationArr = todos.filter(
      (item) => item.title === title && item.contents === contents
    );

    // "02" : 내용 중복 안내
    if (validationArr.length > 0) {
      return getErrorMsg("02", { title, contents });
    }

    // 추가하려는 todo를 newTodo라는 객체로 세로 만듦
    const newTodo = {
      title,
      contents,
      isDone: false,
      id: uuidv4(),
    };

    // todo를 추가하는 reducer 호출
    // 인자 : payload
    dispatch(addTodo(newTodo));

    // state 두 개를 초기화
    setTitle("");
    setContents("");
  };

  return (
    <StyledDiv>
      <form onSubmit={handleSubmitButtonClick}>
        <FlexDiv>
          <RightMarginBox margin={10}>
            <LabledInput
              id="title"
              label="제목"
              placeholder="제목을 입력해주세요."
              value={title}
              onChange={handleTitleChange}
            />
            <HeightBox height={10} />
            <LabledInput
              id="contents"
              label="내용"
              placeholder="내용을 입력해주세요."
              value={contents}
              onChange={handleContentsChange}
            />
          </RightMarginBox>
          <StyledButton type="submit">제출</StyledButton>
        </FlexDiv>
      </form>
    </StyledDiv>
  );
}

export default Input;

 

 

5. 기존 리덕스에서 가져온 리듀서를 주석처리하기

import { addTodo } from "../../modules/todos";
//이거!

 

6. 대신 api의 addTodo import 하기

import { addTodo } from "../../../api/todos";

 

 

7. Input 컴포넌트 안 자바스크립트 영역에, queryClient 선언하기

**잠깐! 여기서는 new useQueryClient 를 쓰지 않는다. 왜? 이미 상위 컴포넌트인 App에서 해주었기 때문에.

**따라서 상위컴포넌트에서 new로 만들어준 것을 이용해서 하나의 흐름으로써 QueryClient를 이용할 수 있다.

더보기

App.jsx

 

import React from "react";
import Router from "./shared/Router";
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

const App = () => {


  return (
  <QueryClientProvider client={queryClient}>
      <Router />
  </QueryClientProvider>
  );
};

export default App;

 

 

Input.jsx

  //리액트 쿼리 관련코드
  const queryClient = useQueryClient();

 

 

8. 변경할 수 있는 함수(mutation 만들기)

 

기본구조

  const mutation = useMutation(api, {
    onSuccess : () => {
      queryClient.invalidateQueries("어떤걸 invalidate 할것인가를 넣기:query key")
    }
  })

 

 

적용후

  const mutation = useMutation(addTodo, {
    onSuccess : () => {
      // queryClient.invalidateQueries("어떤걸 invalidate 할것인가")
      console.log("성공함");
    }
  })

 

 

 

8-1. db랑 cache context와 싱크가 맞지 않는 것을, 싱크를 맞춰주기 -> query invalidation 사용해서 해결하기

  const mutation = useMutation(addTodo, {
    onSuccess : () => {
      queryClient.invalidateQueries("todos");
      console.log("성공함");
    }
  })

 

** 만약 이 과정을 빼먹는다면, 추가버튼을 눌러도 바로 UI에 반영되지 않고(db에는 추가가 되지만) 새로고침을 해야 반영된다.

 

 

 

9. mutation 사용하는 방법

아까 handleSubmitButtonClick 로직 안에다가 

dispatch(addTodo(newTodo)); 대신 아래 구조로 넣기

    mutation.mutate(api로 들어가는 인자);

 

적용 후

    mutation.mutate(newTodo);

 

728x90
반응형