이론과 개념도 함께 참고!
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);
'부트캠프 개발일지 2023-2024 > React 리액트' 카테고리의 다른 글
[11주차] 리액트 : 리액트 쿼리, fresh, stale, staleTime, cache context, inactive data, cache time, GC, Client-Side State, Server-Side State, revalidate, invalidateQueries (0) | 2023.12.17 |
---|---|
[10주차] 리액트심화: React Query 이론과 개념 (2) | 2023.12.06 |
[9주차] 리액트심화: interceptor, instance, .env (0) | 2023.11.29 |
[9주차] 리액트심화: Axios VS Fetch (0) | 2023.11.28 |
[9주차] 리액트심화: 비동기 통신 axios(post, delete,patch) 하기 (0) | 2023.11.28 |