일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 블루투스 헤드셋
- phpmailer
- EUC-KR
- MFC
- 와이브로
- crashlog
- docker
- PDA
- 자바스크립트
- GDI
- VS2008
- protobuf-c
- JavaScript
- Antialiasing
- ClickOnce
- C#
- .net
- API
- 설치제거
- M8200
- C/C++
- php
- self-signed ssl
- Font
- 기념일관리
- 데이터 전달
- net
- 한 번만 실행
- 크래시로그
- plcrashreporter
- Today
- Total
~☆~ 우하하!!~ 개발블로그
React diary - 일기 데이터 가져오기 본문
https://iwoohaha.tistory.com/376 에서 로그인하여 jwt 토큰을 얻어 로컬 스토리지에 저장하고, 로컬 스토리지에 저장된 jwt 토큰값을 처리하는 과정을 살펴보았다.
이번에는 로그인된 상태에서 일기 데이터를 가져오는 로직을 구현해보려고 한다.
일기 데이터를 보여주는 컴포넌트는 Home 이 가장 좋을 것 같다. 이미 Home.js 에는 로그인 상태를 체크하는 로직이 존재한다.
...
const Home = () => {
const { isAuthenticated } = useAuth();
// 로그인상태 확인 후 로그인상태가 아니면 / 로 리디렉션한다.
if (!isAuthenticated) {
return <Navigate to="/" replace />
}
...
Home 컴포넌트가 로딩될 때 로그인된 상태인 경우 RestAPI 를 호출해서 일기 데이터를 가져오는 로직을 구현해보자.
React 에서는 useEffect 함수를 사용해서 컴포넌트가 렌더링될 때 특정 작업을 실행할 수 있다. useEffect 는 컴포넌트가 mount 될 때, unmount 될 때, 컴포넌트가 update 될 때를 선택하여 특정 작업을 처리할 수 있다.
useEffect 함수를 사용하기 위해서는 다음과 같은 import 문이 필요하다.
import React, { useEffect } from 'react';
useEffect 함수는 다음과 같이 사용한다.
useEffect( function, deps );
function 에는 수행할 작업을, deps 는 배열 형태로 검사하고자 하는 특정 값을 넣는다.
useEffect(() => {
console.log('when mount');
}, []);
위와 같이 빈 배열([])을 넣으면 컴포넌트가 맨 처음 렌더링될 때 한번만 실행된다.
deps 에 아무값도 넣지 않는 경우에는 렌더링될 때마다 실행된다.
useEffect(() => {
console.log('when rendering');
});
만약 아래와 같이 특정 값을 넣으면 그 값이 update 될 때 실행된다.
useEffect(() => {
console.log(name);
console.log('name 값이 업데이트될 때');
}, [name]);
위 코드에서는 name 값이 update 될 때 실행되는 경우이다.
그렇다면, Home 컴포넌트가 최초 렌더링될 때 한 번만 실행되는 조건으로 BackEnd 에서 일기 데이터를 가져와 출력하는 로직으로 작성해보도록 하겠다.
import React, {useEffect} from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
const Home = () => {
const { isAuthenticated } = useAuth();
useEffect(() => {
console.log('useEffect');
}, []);
// 로그인상태 확인 후 로그인상태가 아니면 / 로 리디렉션한다.
if (!isAuthenticated) {
return <Navigate to="/" replace />
}
return (
<div style={{ textAlign: 'center', marginTop: '100px' }}>
<h1>홈 화면</h1>
<p>로그인에 성공했습니다. 환영합니다!</p>
</div>
);
};
export default Home;
useEffect 함수를 작성할 부분은 위에서 보듯이 if (!isAuthenticated) { 의 앞이다.
만약 아래와 같이 useEffect 함수의 위치를 if (!isAuthenticated) { 뒤로 옮기면
...
const Home = () => {
const { isAuthenticated } = useAuth();
// 로그인상태 확인 후 로그인상태가 아니면 / 로 리디렉션한다.
if (!isAuthenticated) {
return <Navigate to="/" replace />
}
useEffect(() => {
console.log('useEffect');
}, []);
...
IntelliJ 에서는 아래와 같은 내용의 에러가 표시된다.
ESLint: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?(react-hooks/ rules-of-hooks)
위 내용을 번역하면,
"useEffect" 가 조건부로 호출됩니다. React Hooks 는 모든 구성 요소 렌더링에서 정확히 동일한 순서로 호출되어야 합니다. 조기 반환 후 실수로 React Hook 을 호출했나요?
이다. 즉, 다시 정리하면 조건부로 useEffect 가 호출되도록 코딩해서는 안된다는 말이다.
위 코드를 살펴보면 isAuthenticated 가 true 인 경우에만 useEffect 함수가 호출되는 구조로 코딩되어 있기 때문에 해당 조건부 코드를 useEffect 함수 안으로 옮기던지, 처음에 제시한 것과 같이 if (!isAuthenticated) { 앞에 위치시켜 주어야 하는 것이다.
논리적으로 useEffect 함수는 인증된 상태에서만 동작되어져야 하므로 useEffect 함수 안에 isAuthenticated 값에 대한 분기처리가 필요하다. 따라서 다음과 같이 코딩할 수 있겠다.
...
useEffect(() => {
console.log('useEffect');
if (!isAuthenticated) return;
}, [isAuthenticated]);
// 로그인상태 확인 후 로그인상태가 아니면 / 로 리디렉션한다.
if (!isAuthenticated) {
return <Navigate to="/" replace />
}
...
useEffect 함수 안에 어떤 변수를 사용했다면, deps 영역에 해당 변수를 기록해 주어야 한다. 결론적으로 위 코드에서는 isAuthenticated 변수의 값이 변경될 때 useEffect 함수가 호출된다.
useEffect 함수 상단에서 isAuthenticated 변수값이 true 가 아닌 경우에 return 을 하고 그 아래에 실제로 동작할 함수를 아래와 같이 코딩해준다.
import React, {useEffect, useState} from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import axios from 'axios';
const Home = () => {
const { isAuthenticated } = useAuth();
const [diaryItems, setDiaryItems] = useState([]);
useEffect(() => {
console.log('useEffect');
if (!isAuthenticated) return;
const fetchDiaryItems = async () => {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;// getMonth() 는 0부터 시작하므로
const token = localStorage.getItem('token');
const response = await axios.get(`http://localhost:8080/api/v1/diary`,
{
headers: {
Authorization: `Bearer=${token}` // JWT 토큰 전달
},
params: { year, month }
}
);
setDiaryItems(response.data);
}
fetchDiaryItems();
}, [isAuthenticated]);
...
위에 작성한 코드에서 대해서 설명해보겠다.
가장 먼저 fetchDiaryItems 함수는 async 로 작성해주고, 현재시각(Date)으로부터 year 와 month 값을 구하여 http://localhost:8080/api/v1/diary URL의 RestAPI 를 호출할 때 파라미터로 전달한다.
인증정보는 headers 의 Authorization 값으로 localStorage 에 저장되어 있는 jwt 토큰값을 전달한다.
RestAPI 호출 결과로 받은 데이터는 setDiaryItems 함수로 상태(state)를 설정한다.
RestAPI 를 호출할 때 axios 를 사용하는데, 터미널에서 아래 명령어로 axios 패키지를 설치해 주어야 한다.
npm install axios
여기에서 호출하고 있는 /api/v1/diary URL 의 RestAPI 는 diary Backend 프로젝트에서 새롭게 APIDiaryController.java 를 생성한 후에 아래와 같이 코딩해주면 된다.
package com.woohahaapps.study.diary.controller;
import com.woohahaapps.study.diary.domain.Diary;
import com.woohahaapps.study.diary.service.DiaryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1")
@Slf4j
public class APIDiaryController {
private final DiaryService diaryService;
public APIDiaryController(DiaryService diaryService) {
this.diaryService = diaryService;
}
@GetMapping("/diary")
public List<Diary> GetDiaryItems(@RequestParam(required=true) Integer year,
@RequestParam(required=true) Integer month) {
return diaryService.GetAllDiaries(year, month);
}
}
다시 react 프로젝트로 돌아와서 diaryItems 상태에 저장된 데이터를 화면에 표시하는 코드를 구현해보자.
...
return (
<div style={{textAlign: 'center', marginTop: '100px'}}>
<h1>홈 화면</h1>
<p>로그인에 성공했습니다. 환영합니다!</p>
<ul>
{diaryItems.map((item) => (
<li key={item.id}>
<h3>{item.diary_date}</h3>
<p>{item.diary_content}</p>
</li>
))}
</ul>
</div>
);
...
원래 작성되어 있던 코드 아래에 <ul> ~ </ul> 의 코드를 삽입시켜보았다.
이렇게 수정한 결과로 아래와 같이 데이터가 표시되는 것을 확인할 수가 있게 된다.
각 날짜의 일기 데이터를 출력하기 위해서 DiaryItem 이라는 이름의 컴포넌트를 만들어볼 수도 있겠다.
import React from 'react';
const DiaryItem = ({ id, date, content }) => {
return (
<div id={id}>
<div><h3>{date}</h3></div>
<div>{content}</div>
</div>
);
};
export default DiaryItem;
위와 같이 구현한 DiaryItem 컴포넌트를 사용하는 코드로 수정된 결과는 다음과 같다.
...
return (
<div style={{textAlign: 'center', marginTop: '100px'}}>
<h1>홈 화면</h1>
<p>로그인에 성공했습니다. 환영합니다!</p>
<ul>
{diaryItems.map((item) => (
// <li key={item.id}>
// <h3>{item.diary_date}</h3>
// <p>{item.diary_content}</p>
// </li>
<DiaryItem
id={item.id}
date={item.diary_date}
content={item.diary_content}
/>
))}
</ul>
</div>
);
...
위와 같이 수정한 후에 React 프로그램을 실행시켜보면 콘솔에 아래와 같이 표시되는 것을 볼 수가 있다.
모든 항목에 유일값으로 key 속성을 지정하는 것을 추천하는 내용인데, 아래와 같이 추가로 수정해 주었다.
...
<DiaryItem
key={item.id}
id={item.id}
date={item.diary_date}
content={item.diary_content}
/>
...
key prop 는 React 가 DOM 노드를 효율적으로 업데이트하기 위해서 리스트에서 각 요소를 고유하게 식별하게 도와주는 요소이다. 이 값이 없으면 성능 저하 및 예기치 않은 UI 동작 문제가 발생할 수 있다.
'React' 카테고리의 다른 글
React diary - jwt 로그인하기 - React Context (0) | 2024.12.10 |
---|---|
React diary - 로그인 페이지로 이동하자 - 라우터 (0) | 2024.12.09 |
React 프로젝트 생성 오류 (Trouble Shooting) (1) | 2024.12.09 |
React - 컴포넌트에 값 저장하기 (0) | 2024.11.12 |
React - 컴포넌트 이벤트 핸들러 함수 추가하기 (0) | 2024.11.12 |