본문 바로가기

React

[Springboot/React] React에서 api 호출하기

프론트가 아니어서 자세히는 모르지만 기억나는대로 간단하게 정리해 보았다.

react 프로젝트 생성

프로젝트 생성하려는 위치로 이동한다.
npm과 node.js는 설치가 되어있어야한다.

프로젝트 생성

npx create-react-app my-react

 

명령어로 생성

react 실행

npm start

 

이 화면이 뜬다면 완료.

npm start
실행시
> my-react@0.1.0 start
> react-scripts start

'react-scripts'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는
배치 파일이 아닙니다.
라는 오류가 발생한다면
npm install react-scripts으로 npm 재설치를 해주면 된다.

api 호출

Proxy 설정

React 애플리케이션에서는 API 호출 시 프록시(proxy)를 설정하여 API 서버로 요청을 보낼 수 있다.
따라서 API 서버의 엔드포인트들은 /api를 포함한 경로로 정의하였고, React 애플리케이션에서 setupProxy.js 설정파일을 통해 /api를 포함한 경로로 호출 시 API서버로 리다이렉트 해주었다.

axios 라이브러리 설치

npm install axios

 

setupProxy.js

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  // /api로 시작하는 모든 요청을 localhost:8081로 프록시
  app.use(
      '/api',
      createProxyMiddleware({
        target: 'http://localhost:8081',
        changeOrigin: true,
      })
  );
};

데이터를 불러올때

api 호출

const [users, setUsers] = useState([]);
try {
       const response = await axios.get('/api/get'); // API endpoint에 맞게 URL 수정
        setUsers(response.data); // API 응답 데이터를 상태에 설정
     } catch (error) {
          console.error('Error fetching data:', error);
     }

axios를 통해 api를 호출하고 응답 데이터를 상태에 설정해준다.
주로 async/await 구문을 사용하여 비동기적으로 API를 호출하고 응답을 처리

예시

http://localhost:8081/api/get api를 호출하고,
응답 데이터가 다음과 같을때

[
    {
        "id": "qweqwe",
        "pw": "123123",
        "birth": "1997-11-26",
        "myList": [
            "11",
            "22"
        ],
        "imgPath": "https://firebasestorage.googleapis.com/v0/b/<>/o/IMG_3842.JPG?alt=media"
    },
    {
        "id": "yooon",
        "pw": "qwe123",
        "birth": "2002-04-24",
        "myList": [
            "11",
            "22"
        ],
        "imgPath": "https://firebasestorage.googleapis.com/v0/b/<>/o/firebase.png?alt=media"
    }
]

api 호출을 테스트하기위해 간단한 화면을 만들었다.

 

전체 코드는 다음과 같다.

UserList.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const UserList = () => {
    const [users, setUsers] = useState([]);

    useEffect(() => {
        const fetchUsers = async () => {
            try {
                const response = await axios.get('/api/get'); // API endpoint에 맞게 URL 수정
                setUsers(response.data); // API 응답 데이터를 상태에 설정
            } catch (error) {
                console.error('Error fetching data:', error);
            }
        };

        fetchUsers();
    }, []);

    return (
        <div>
            <h2>User List</h2>
            <ul className="user-list">
                {users.map(user => (
                    <li key={user.id} className="user-item">
                        <div className="user-info">
                            <div><strong>ID:</strong> {user.id}</div>
                            <div><strong>Password:</strong> {user.pw}</div>
                            <div><strong>Birth Date:</strong> {user.birth}</div>
                            <div>
                                <strong>My List:</strong>
                                <ul className="nested-list">
                                    {user.myList.map((item, index) => (
                                        <li key={index}>{item}</li>
                                    ))}
                                </ul>
                            </div>
                            <img src={user.imgPath} alt="이미지" style={{ width: '200px'}}/>
                        </div>
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default UserList;

화면을 띄우기 위해 App.js도 수정해주었다.


App.js

import './App.css';
import UserList from "./UserList";
import CreateUserForm from "./createUserForm";
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <div className="App">
        <Router>
            <Routes>
                <Route path="/user" element={<UserList />} />
                <Route path="/usercreate" element={<CreateUserForm />} />
            </Routes>
        </Router>
      {/*<UserList />*/}
    </div>
  );
}

export default App;

 

결과화면

데이터를 저장할때

api에서 요구하는 데이터 형식에 맞추어 전송하면 된다.
주로 async/await 구문을 사용하여 비동기적으로 API를 호출하고 응답을 처리

  • 파일을 함께 전송하는 경우 헤더를 'Content-Type': 'multipart/form-data'으로 지정해준다.
    const formDataForRequest = new FormData();
    formDataForRequest.append('user', JSON.stringify(formData)); // Test 객체를 JSON 문자열로 변환하여 FormData에 추가
    formDataForRequest.append('file', selectedImg); // 파일 객체를 FormData에 추가
    
    const response = await axios.post('/test/create', formDataForRequest, {
    headers: {
    'Content-Type': 'multipart/form-data', // 파일 업로드 시에는 Content-Type을 지정해야 합니다.
    },
    });

 

예시

컨트롤러에서 요구하는 데이터가 Json형식의 데이터와 파일 데이터 두가지일때

 

controller

@PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ResponseEntity<String> createUser(@RequestParam("user") String testDataJson,
                                             @RequestPart("file") MultipartFile file) throws Exception{
        String response = testService.createUser(testDataJson,file);
        return ResponseEntity.ok(response);
    }
  • 이때 user파라미터는 User형식의 데이터를 string 형식으로 받아 backend에서 User클래스로 매핑해준다.

User.java

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class User {
   private String id;
   private String pw;
   @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
   private Date birth;
   private List<String> myList;
   private String imgPath;

  }



React에서 전체코드는 다음과 같다.

createUserForm.js

import React, { useState } from 'react';
import axios from 'axios';
import Chip from '@mui/material/Chip';
import Box from '@mui/material/Box';
import {ListItem} from '@mui/material';
import { TextField } from '@mui/material';


const CreateUserForm = () => {
    const [formData, setFormData] = useState({
        id: '',
        pw: '',
        birth: '',
        myList: [],
        imgPath: "qwe123",
    });

    const [selectedImg, setSelectedImg] = useState([]);//첨부한 이미지
    const handleInputChange = (event) => {
        const { name, value } = event.target;
        setFormData({
            ...formData,
            [name]: value,
        });
    };

    const handleKeyPress = (event) => {
        if (event.key === 'Enter' && event.target.value.trim() !== '') {
            const newItem =event.target.value.trim();

            setFormData((prevFormData) => ({
                ...prevFormData,
                myList: [...prevFormData.myList, newItem], // 이전 myList 배열에 새 label 값 추가
            }));
            event.target.value = ''; // 입력 필드 비우기
            event.preventDefault(); // 엔터키 이벤트의 기본 동작인 폼 제출을 막음
        }
    };
    const handleChipDelete = (itemToDelete) => () => {
        const updatedList = formData.myList.filter((item) => item !== itemToDelete);
        setFormData({
            ...formData,
            myList: updatedList,
        });
    };
    const handleFileChange = (event) => {
        setSelectedImg(event.target.files[0]); // 첫 번째 파일만 선택
    };


    const handleSubmit = async (event) => {
        event.preventDefault();
        console.log("click");
        console.log(formData);
        try {
            const formDataForRequest = new FormData();
            formDataForRequest.append('user', JSON.stringify(formData)); // Test 객체를 JSON 문자열로 변환하여 FormData에 추가
            formDataForRequest.append('file', selectedImg); // 파일 객체를 FormData에 추가

            const response = await axios.post('/test/create', formDataForRequest, {
                headers: {
                    'Content-Type': 'multipart/form-data', // 파일 업로드 시에는 Content-Type을 지정해야 합니다.
                },
            });

            console.log(response.data); // API 응답 데이터 확인
            // 성공적으로 요청을 처리했을 때 필요한 작업을 수행합니다.
        } catch (error) {
            console.error('API 요청 실패:', error);
            // 요청 실패 시에는 오류 처리 로직을 추가합니다.
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
            <label>
                ID:
                <input type="text" name="id" value={formData.id} onChange={handleInputChange} />
            </label>
            <br />
            <label>
                Password:
                <input type="password" name="pw" value={formData.pw} onChange={handleInputChange} />
            </label>
            <br />
            <label>
                Birth:
                <input type="date" name="birth" value={formData.birth} onChange={handleInputChange} />
            </label>
            <br />
            <label style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
                My List (comma-separated):
                {/*<input type="text" name="myList" value={formData.myList.join(',')} onChange={handleInputChange} />*/}
                <Box component="ul">
                    {formData.myList.map((data, index) => (
                        <ListItem key={index}>
                            <Chip
                                label={data}
                                onDelete={data === 'React' ? undefined : handleChipDelete(data)}
                            />
                        </ListItem>
                    ))}
                    <ListItem>
                        <TextField
                            placeholder="Add item..."
                            onKeyDown={handleKeyPress}
                        />
                    </ListItem>
                </Box>
            </label>
            <br />
            <label>
                Image File:
                <input type="file" onChange={handleFileChange} />
            </label>
            <br />
            <button type="submit">Create User</button>
            </div>
        </form>
    );
};

export default CreateUserForm;

결과화면

리액트를 실행시킨 후 해당 화면으로 접속하면 다음과 같은 화면을 볼 수 있다.

데이터를 입력한 후 Create User 버튼을 눌러 데이터를 전송하면 api를 통해 데이터 저장을 할 수 있다.

api 호출 후 응답데이터를 확인할 수 있다.

  • 실패
  • 성공

설치 필요한 npm

npm install @mui/material
npm install @emotion/styled
npm install @emotion/react