[SeSAC Front-end] [React] 과일 데이터 베이스 만들기 (with Firebase)

Harimad·2022년 12월 13일
0

React

목록 보기
10/18
post-thumbnail

Intro

  • 이번 장에서는 아래에 게시된 과일 데이터 베이스를 만들어 보겠습니다.
  • firebase를 이용해서 서버데이터를 저장하고 react로 가져와서 사용하는 작업을 하겠습니다.
  • 저는 CSS 파일은 생성해 놓았지만, CSS 코드는 적용하지 않았습니다. 기호에 맞게 CSS를 적용하시면 좋습니다.
  • 과일 정보 입력 -> DB추가
  • 과일 정보 사이트

본론


1. 컴포넌트 레이아웃


2. 파일구조


📄 App.js

  • 먼저 react-router-dom 모듈을 설치합니다.
npm install react-router-dom
  • firebase 모듈도 설치합니다.
npm install firebase
  • firebase 데이터 조회, 수정하는 함수를 만듭니다.
  • 라우터를 설정하기 위해서 컴포넌트를 가져온뒤 아래와 같이 라우터를 설정해 줍니다.
import React, { useState, useEffect } from 'react'
import { db } from './firebase'
import { collection, doc, getDocs, setDoc } from 'firebase/firestore'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import Root from './components/Root'
import Home from './components/Home'
import Error from './components/Error'
import Products from './components/Products'
import Product from './components/Product'

const App = () => {
  const fruitCollection = collection(db, 'fruits')
  const [fruits, setFruits] = useState([])

  // Firebase 데이터 조회
  useEffect(() => {
    async function getFruits() {
      const data = await getDocs(fruitCollection)
      setFruits(data.docs)
    }
    getFruits()
  }, [])

  // Firebase 데이터 수정
  async function makeFruit(fruit) {
    await setDoc(doc(fruitCollection), {
      name: fruit.name,
      season: fruit.season,
      color: fruit.color,
      taste: fruit.taste,
      count: fruit.count,
      price: fruit.price,
    })
  }

  // 라우터 정의
  const router = createBrowserRouter([
    {
      path: '/',
      element: <Root />,
      errorElement: <Error />,
      children: [
        {
          index: true,
          element: <Home makeFruit={makeFruit} />,
        },
        {
          path: '/products',
          element: <Products fruits={fruits} />,
        },
        { path: '/products/:productId', element: <Product /> },
      ],
    },
  ])

  return <RouterProvider router={router} />
}
export default App

📄 firebase.js

  • firebase 설정방법이나 react로 데이터를 가져오는 방법은 제 블로그 게시물을 확인해 주세요!
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  appId: process.env.REACT_APP_FIREBASE_WEB_APP_ID,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
}
// console.log(firebaseConfig)

// Firebase 초기화
const app = initializeApp(firebaseConfig)

// Firebase 객체 저장
export const db = getFirestore(app)

📄 src / Root / index.js

  • react-router-dom 에서 Outlet 컴포넌트를 가져와 사용합니다.
  • Outlet은 자식 라우트 요소들을 랜더하기 위한 부모 라우트 요소 입니다.
import React from 'react'
import { Outlet } from 'react-router-dom'
import Nav from '../Nav'

const Root = () => {
  return (
    <div>
      <Nav />
      <Outlet />
    </div>
  )
}

export default Root

📄 src / Nav / index.js

import React from 'react'
import { Link } from 'react-router-dom'

const Nav = () => {
  return (
    <div>
      <Link to="/" style={{ marginRight: '1rem' }}>
        Home
      </Link>
      <Link to="/products">Products</Link>
    </div>
  )
}

export default Nav

📄 src / Home / index.js

import React from 'react'
import Form from '../Form'

const Home = ({ makeFruit }) => {
  return (
    <div>
      <h1>🍓과일 데이터베이스🍆</h1>
      <Form makeFruit={makeFruit} />
    </div>
  )
}

export default Home

📄 src / Form / index.js

  • App 컴포넌트에서 firebase에 데이터를 수정할 수 있는 함수(makeFruit)를 받아옵니다.
  • input에 값을 채우고 버튼을 입력하면 firebase에 데이터가 추가됩니다.
import React, { useState } from 'react'

const Form = ({ makeFruit }) => {
  const [input, setInput] = useState({})
  const changeHandler = e => {
    setInput({ ...input, [e.target.name]: e.target.value })
  }

  const submitHandler = () => {
    makeFruit(input)
  }
  return (
    <div>
      <div>
        <label>이름</label>
        <input type={'text'} name="name" onChange={changeHandler}></input>
      </div>
      <div>
        <label>계절</label>
        <input type={'text'} name="season" onChange={changeHandler}></input>
      </div>
      <div>
        <label>색상</label>
        <input type={'text'} name="color" onChange={changeHandler}></input>
      </div>
      <div>
        <label></label>
        <input type={'text'} name="taste" onChange={changeHandler}></input>
      </div>
      <div>
        <label>수량</label>
        <input type={'number'} name="count" onChange={changeHandler}></input>
      </div>
      <div>
        <label>가격</label>
        <input type={'number'} name="price" onChange={changeHandler}></input>
      </div>
      <div>
        <button onClick={submitHandler}>추가</button>
      </div>
    </div>
  )
}

export default Form

📄 src / Products / index.js

  • firebase에서 데이터를 전체조회하는 함수(getFruits1)를 생성합니다.
  • 전체 조회한 데이터를 data에 저장합니다.
  • 전체 데이터를 map() 함수를 돌리고 Item 컴포넌트에 과일 하나씩 props로 내려줍니다.
  • 그리고 fruit의 id 값도 props로 내려줍니다.
  • Products 컴포넌트가 랜더링되면 과일 데이터 전체가 화면에 보이게 됩니다.
import React, { useState, useEffect } from 'react'
import Item from '../Item'
import { db } from '../../../firebase'
import { getDocs, collection } from 'firebase/firestore'
const Products = () => {
  const [data, setData] = useState([])
  const fruitCollection = collection(db, 'fruits')

  async function getFruits1() {
    const data = await getDocs(fruitCollection)
    setData(data.docs)
  }

  useEffect(() => {
    getFruits1()
  }, [data.length])

  return (
    <div>
      <h1>전체 상품 리스트</h1>
      <table>
        <thead>
          <tr>
            <th>상품명</th>
            <th>수확시기</th>
            <th>색상</th>
            <th>당도</th>
            <th>수량</th>
            <th>가격</th>
            <th>상세정보</th>
          </tr>
        </thead>
        <tbody>
          {data.map((fruit, idx) => {
            return <Item key={idx} data={fruit.data()} id={fruit.id} />
          })}
        </tbody>
      </table>
    </div>
  )
}

export default Products

📄 src / Item / index.js

  • 상세 정보를 눌렀을 때 라우팅이 되도록 useNaigate를 react-router-dom 에서 가져옵니다.
  • 상세 정보를 눌렀을 때 id 값으로 라우팅을 합니다.
  • navigate로 라우팅을 할 때 props로 전달을 두 번째 인자 안에 객체({})형태로 담아줍니다.
import React from 'react'
import { useNavigate } from 'react-router-dom'

const Item = ({ data, id }) => {
  const navigate = useNavigate()
  return (
    <tr>
      <td>{data.name}</td>
      <td>{data.season}</td>
      <td>{data.color}</td>
      <td>{data.taste}</td>
      <td>{data.count}</td>
      <td>{data.price}</td>
      <td>
        {
          <button
            onClick={() => {
              navigate(id, { state: data })
            }}
          >
            상세정보
          </button>
        }
      </td>
    </tr>
  )
}

export default Item

📄 src / Product / index.js

  • 라우팅에서 query값을 받기 위해서 react-router-dom 에서 useParams를 가져 옵니다.
  • useNavigate를 이용해서 props가 넘어 왔을 때 데이터를 받기 위해서 useLocation을 react-router-dom에서 가져옵니다.
  • firebas 데이터를 수정하는 함수(updateFruit)를 생성하고 수정 버튼의 클릭 이벤트에 넣어줍니다.
  • firebase 데이터를 삭제하는 함수(deleteFruit)를 생성하고 삭제 버튼의 클릭 이벤트에 넣어줍니다.
  • 수정이나 삭제 버튼을 눌렀을 때 '/products'로 라우팅 되도록 만듭니다.
import React, { useState, useEffect } from 'react'
import { useParams, useLocation, useNavigate } from 'react-router-dom'
import { collection, doc, getDoc, updateDoc, deleteDoc,} from 'firebase/firestore'
import { db } from '../../../firebase'

const Product = () => {
  const [fruit, setFruit] = useState({})
  const { productId } = useParams()
  const { state } = useLocation()
  const navigate = useNavigate()
  const fruitCollection = collection(db, 'fruits')

  useEffect(() => {
    setFruit({ ...state })
  }, [])

  console.log(fruit)

  async function updateFruit() {
    await updateDoc(doc(fruitCollection, productId), {
      name: fruit.name,
      season: fruit.season,
      color: fruit.color,
      taste: fruit.taste,
      count: fruit.count,
      price: fruit.price,
    })
    navigate('/products')
  }

  async function deleteFruit() {
    await deleteDoc(doc(fruitCollection, productId))
    navigate('/products')
  }

  const changeHandler = e => {
    setFruit(prev => ({ ...prev, [e.target.name]: e.target.value }))
  }

  return (
    <div>
      <h1>{productId} 제품 상세 페이지</h1>
      <div>
        <label>상품이름</label>
        <input value={fruit.name} name="name" onChange={changeHandler}/>
      </div>
      <div>
        <label>수확시기</label>
        <input value={fruit.season} name="season" onChange={changeHandler}/>
      </div>
      <div>
        <label>상품색상</label>
        <input value={fruit.color} name="color" onChange={changeHandler}/>
      </div>
      <div>
        <label>상품당도</label>
        <input value={fruit.taste} name="taste" onChange={changeHandler}/>
      </div>
      <div>
        <label>생상수량</label>
        <input value={fruit.count} name="count" onChange={changeHandler}/>
      </div>
      <div>
        <label>상품가격</label>
        <input value={fruit.price} name="price" onChange={changeHandler}/>
      </div>
      <div>
        <button onClick={updateFruit}>수정</button>
        <button onClick={deleteFruit}>제거</button>
      </div>
    </div>
  )
}

export default Product

📄 src / Error / index.js

  • 컴포넌트 라우팅을 하다가 오류가 났을 때 Error 컴포넌트를 보여줍니다.
  • 안에 내용은 자유롭게 작성하도록 합니다.
import React from 'react'

const Error = () => {
  return <div>Error</div>
}

export default Error

나가면서

  • 지금까지 firebase를 이용해서 react에 데이터를 받아오는 과정을 통해서, 간단한 과일 데이터 베이스를 만들어 보았습니다.
  • 게시판이나 Todo 리스트와 로직은 비슷합니다. 생성, 조회, 수정, 삭제하는 기능을 이용하는 것이죠.
  • 추가적으로 라우팅 설정을 통해서 페이지를 동적으로 사용하도록 하였습니다.
  • 스타일링은 개인의 기호에 맞게 styled-component, MUI, SASS 등의 방법을 통해서 줄 수 있습니다.
  • 이번 과정을 통해서 라우팅 설정하는 방법에 대해서 한번 더 배우는 시간이 되었습니다.
  • 그리고 Firebase를 통해서 클라우드에 서버를 두어서 데이터를 조작하는 방법을 알게 되었습니다.
  • 향후 데이터를 서버에 저장할 때 mongodb뿐만 아니라 firebase를 이용하는 것도 좋은 방법이라는 것을 깨닫게 되었습니다.

참고

profile
Here and Now. 🧗‍♂️

0개의 댓글