React + firebase (1)

eg_kim·2024년 9월 2일

React

목록 보기
8/10
post-thumbnail

리액트와 파이어베이스를 함께 이용해보자!

파이어베이스 설치하기

npm install fire-tools
npm install firebase

파이어 베이스를 설치하고 나는 로그인 되어있던 파이어베이스 계정이 있어서

firebase logout -> firebase login

순서로 계정 로그인을 했다.

파이어베이스에서 데이터베이스 생성하기

새로운 프로젝트를 생성하고
FireStore DataBase를 추가하고
프로젝트 기간이 30일로 설정되어있기 때문에

이렇게 설정을 바꿔두었다!

프로젝트 파일에 파이어베이스 연동하기

생성된 파이어베이스 프로젝트에서 웹앱을 추가한다.

그리고 파이어베이스에 자동으로 생성된 SDK를 복사한 뒤에
vscode로 돌아가서

파이어 베이스 연동을 위해 SDK 코드를 넣을 수 있도록
src 안에 firebase 폴더를 생성해서 index.js라는 파일을 생성해도 되고,
폴더를 생성하지 않고 firebase.js 라는 파일을 생성해도 된다.
생성한 js 파일에 SDK 코드를 모두 복사해서 붙여넣기 한다.

파이어베이스 데이터 세팅하기

기능 생성을 생성하기 전에 시드 데이터를 생성했다.

원하는 이름의 컬렉션을 만들고 (컬렉션 이름을 추후에 react-todos로 변경했다.)

문서를 추가해서 자동 아이디 생성, 필드는 content라는 제목으로 사용하고 그 안의 내용은 string(문자열)로 아무거나 적었다.


하나의 필드 안에 여러개의 시드데이터를 생성하기 위해 문서 추가를 했다.
컬렉션 추가 아니고, 문서 추가를 해야한다!


문서를 추가하면 이렇게 랜덤으로 생성된 자동 아이디와 컨텐츠 내용을 확인할 수 있다.

tip!

파이어 베이스를 사용하는 방법은 빌드라는 카테고리 안에 들어있는데


이런식으로 파이어베이스를 초기화 하는 방식을 알려주는 등 사용하는 방법과 코드가 안내되어있다!

따라서 SDK 코드에 파이어베이스초기화를 import하는 코드가 없었기 때문에

이 부분을 가져오고

이렇게 db 안에 코드를 담은 뒤


export 해서 상수를 내보내기 했다.

리액트 세팅하기

데이터베이스 가져오기

App.css를 모두 비우고 App.js 파일도 초기화했다.

import React from 'react'
import { collection } from 'firebase/firestore'
import { db } from './firebase'

export default function app() {
  const todosCollectionRef = collection(db,"react-todos") //첫번째 인자는 index.js에 담은 상수 db, 두번째 인자는 컬렉션 이름 "todos"



  return (
    <div>
      app
    </div>
  )
}

import {collection} from 'firebase/firestore'은 파이어베이스의 컬렉션을 가져오는 문장이고 index.js에 담아두었던 상수 db도 불러왔다.


공식 문서에서 이렇게 변수에 담아 컬렉션을 불러오도록 해서
나도 똑같이 변수 todosCollectionRef에 db와 컬렉션이름을 담아 컬렉션을 가져왔다.


(시드 데이터 수정)

데이터 수정이 필요해서 isDone이라는 문서 안의 필드를 추가하고
boolean type으로 1개만 True로 설정해놓고 나머지 문서들은 false로 설정해두었다.

컬렉션에서 문서 가져오기

컬렉션에서 문서를 가져올 땐 getDoc() 이라는 기능을 사용하는데 여러가지 문서를 가져올 땐 getDocs()를 사용한다.

useEffect(()=>{
    const getList = async () => {
      const data = await getDocs(todosCollectionRef)
},[])

일단 문서를 가져오기 위해 getDocs를 사용했는데, 데이터를 모두 가져온 다음에 리스트(데이터베이스 안에 담겨있는 내용, 시드데이터)를 출력하기 위해 비동기식을 사용했다(async, await)
이렇게 하면 시드데이터가 출력된다 !


데이터 입력하기(ADD)

데이터를 입력하고, ADD 버튼을 누르면 화면에 출력되도록 만들거다.
이때 필요한 훅은 useState()이다. 데이터 입력이 되면 새로고침 되어서 출력 되어야 하기 때문에!
그리고 데이터가 입력될 때 할 일 옆에 리스트를 추가한 날짜가 입력되도록 했다.

mysql의 경우 데이터를 추가할 때 insert into TN(name,id) values ('길동','hong') 형식을 사용했다면 파이어베이스도 똑같이 키:"값", 키:"값"의 형식으로 데이터를 추가한다. (name: '길동', id: 'hong')


파이어베이스도 배열 안에 객체 형식으로 데이터가 담겨있으므로 데이터를 입력(추가)할 때 객체 형식으로 입력하도록한다.

먼저 html 코드를 살펴보면

 <div>
      <h2>오늘 할 일</h2>
      
        <hr/>
        <p>
        <input
        type='text'
        placeholder='to do'
        value={newList}
        onChange={e=>{
          setNewList(e.target.value)
        }}
        />
        <button
        onClick={createList}
        >ADD</button>
      </p>
      {
        todos.map(
          value=>(
            //console.log(value)
            <h3 key={value.id}>
              <span className="date">{value.d_date}</span>
             </h3>

          ))
      }
      
    </div>

input text를 생성한 뒤 placeholder로 텍스트 상자 안에 to do라는 글자가 고정되어 있을 수 있도록 했다.
그리고 value와 onChange를 이용해서 값이 바뀔 수 있도록 했다.
버튼을 누르면 createList 함수가 적용되고 map()을 이용해서 필요한 데이터(시간)를 불러올 수 있도록 했다. 컴포넌트를 나누지 않고 app.js에서 한꺼번에 작업하였다.

//newList, setNewList

 const [todos, setTodos] = useState([])
  const [newList, setNewList] = useState('')
    const todosCollectionRef = collection(db,"react-todos") //첫번째 인자는 index.js에 담은 상수 db, 두번째 인자는 컬렉션 이름 "todos"
 useEffect(()=>{
    const getList = async () => {
      const data = await getDocs(
      //todosCollectionRef
      query(todosCollectionRef, orderBy('timestamp'))
    )

      //console.log(data)
      /*data.forEach((doc)=>{
        //doc.data() is never undefined for query doc snapshots
        console.log(doc.id, "=>", doc.data())
      })*/

     setTodos(
        data.docs.map(doc=>(
        {...doc.data(), id:doc.id}
        )
      )
    )
      }
        
    //console.log(todos)
      getList()
      setChanged(false)
  },[changed])

  
//date

const date = new Date()
  const nowDate = date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate()
  //console.log(nowDate)



//create

const createList = async ()=>{
    await addDoc(todosCollectionRef, {
      content: newList,
       //isDone: false,
      d_date: nowDate,  //json파일의 장점. 유연하다!
      timestamp: date //또는 date
    })
    setNewList("");
    setChanged(true)
  }

배열 안에 객체 형태로 받아야 하기 때문에 첫번째 useState는 빈 배열로 두었고,
새로운 데이터는 문자(string)형식으로 추가되기 때문에 '' 빈 문자열로 두었다.

useEffect안에는 쿼리문을 담아서 저장된 데이터 중 timestamp에 대한 데이터를 가져올 수 있도록 했다.(이유는 아래에 설명)
또한 setTodos에 map을 사용하여 각각의 리스트를 구분하기 위해 아이디를 가져오도록 했다.

create에서는 addDoc()을 사용하여 문서를 추가했는데 위에서 언급했듯이 데이터를 추가할 때 객체 형식으로 추가했다.

setNewList가 비어있는 이유와 setChanged(true)는 아래의 내용에서 설명!


데이터 정렬하기

기본적으로 추가된 데이터의 순서는 데이터 베이스에 자동으로 생성된 id의 알파벳순으로 나열된다. 리스트 상에서 보았을때 차례대로 입력되지 않고, 순서가 섞인것 처럼 보이기 때문에 데이터를 입력한 순서대로 정렬하는 코드를 추가해주었다.

firebase에서 데이터를 정렬하거나 제한을 할 땐 orderBy() 그리고 limit() 메소드를 사용할 수 있다.
orderBy안에는 어떤 항목을 어떻게 정렬할 것인지 입력해야 한다.

mysql에서의 코드로 생각해보면
insert into tn(name,id) value("aa","bb)
{
name:"aa",
id:"bb"
}

select FN from TN order by idx asc/desc 이다.
그런데 스크립트 문법으로 변환되어서 order과 by를 붙여쓰고, B가 대문자가 된 것이다!


firebase에서 데이터를 오름차순/내림차순으로 정렬하기
query(DBConn, orderBy('기준항목',''정렬'))

asc이면 기본값이므로 생략해도 괜찮은데, 만약 이중정렬을 하게 된다면 (예를 들어 이름순으로 정렬했는데 같은 이름이 있는 경우) 먼저 이름순을 내림차순으로 정렬하고 번호를 오름차순으로 정렬하는 등의 경우에 필요할 수 있다.

리스트가 문서의 알파벳 순서대롤 출력되는 것을 쿼리를 통해 날짜(시간)순으로 정렬을 했는데 같은 날짜의 데이터는 또 다시 문서의 아이디 순서로 출력되었다. 따라서 날짜 데이터를 두 개를 생성하여 한가지는 보여지는 용도(데이터를 입력하면 보여지는 날짜), 한 가지는 정렬하는 용도(아이디 순서가 아닌 시간 순서로 정렬)로 사용할 것이다.

자바스크립트에서 시간을 구하는 문법
1. new Date()
2. Date.now() - 타임스탬프

이렇게 두 가지가 있는데
new Date()는 데이터 입력 부분에서 언급을 했고, 타임스탬프에 대해 정리를 해보자면 시간을 초단위로 계산하여 생성해주는 메소드이다.

//create
  const createList = async ()=>{
    await addDoc(todosCollectionRef, {
      content: newList,
       //isDone: false,
      d_date: nowDate,  //json파일의 장점. 유연하다!
      timestamp: Date.now() //또는 date
    })
    setNewList("");
    setChanged(true)
  }

이렇게 정의할 수 있다!
그런데 잘 출력이 되다가 타임스탬프를 적용했는데도 순서가 바뀌는 경우가 있어서(시간을 읽는데 오류가 있을 수도 있다고 함) 결국 timestamp 자리에 date(=new Date())를 담았다.

이러한 방법도 있다는 것을 알고 넘어가기!

같은 날짜에 생성해서 나타나는 오류 때문에 적용하는 또 다른 방법에는 date를 생성할 때 한자리수 숫자 앞에 0이 올 수 있도록 코드를 짜고, 0이 10보다 작을때 먼저 생성된 데이터, 두자리수가 될 땐 나중에 생성된 데이터로 구분할 수 있도록 하는 방법도 있다!


데이터 업데이트 하기(edit)

리스트를 수정하는 기능을 만들기 위해 업데이트 메소드를 사용했다!

데이터를 업데이트 할 땐 updateDoc()이라는 메서드를 사용한다.
doc()안에는 db, 컬렉션 이름 그리고 업데이트할 대상을 적는다.

  //update
  const updateList = async(id, content)=>{
    //console.log(id,'/',content)
    const msg = window.prompt("TO DO", content)

    if(msg){

      const listDoc = doc(todosCollectionRef,id)
      const editField = {
        content:msg,
        d_date:nowDate
      }

      await updateDoc(listDoc, editField)


    }
    setChanged(true)

  }

content = 컬렉션 이름
id를 이용해 리스트를 구분하고, content안의 내용이 msg로 변경되면
listDoc에 있는 내용을 editField에 담긴 데이터로 업데이트 된다는 의미이다.


데이터 삭제하기(delete)

데이터를 삭제하는 버튼도 생성해서 웹에서 삭제하면 데이터 베이스 안의 내용도 삭제되도록 하였다.

deleteDoc()이라는 메서드를 사용하고, doc()안에 db, 컬렉션 이름, 어떤 데이터를 삭제할 것인지 적는 것이다.
update와 사용하는 형식이 거의 유사하다!

//delete
    const deleteList = async(id)=>{
      const isTrue = window.confirm("삭제하시겠습니까?")

      if(isTrue){
        const listDoc = doc(todosCollectionRef, id)
        await deleteDoc(listDoc)
      }
      setChanged(true)

    }
    
    //http
    
   

사용자가 실수를 방지할 수 있도록 confirm을 사용해서 확인, 취소를 할 수 있도록 했고
isTrue가 참인 경우 조건문이 실행되어서 deleteDoc()가 비동기식으로 실행된다.


상태 완료 표시하기(complete/update(2))

todoList에서 할 일을 다 했으면 표시할 수 있는 체크 기능이다!
체크박스나 토글을 사용하지 않았고, button을 이용해서 생성했다.
isDone이라는 변수를 만들어서 true와 false를 통해 css가 실행되도록 코드를 작성했다.

//isDone
    const isDoneList = async (id,isDone)=>{
      //console.log(id,'/',isDone)
      //console.log('isDOne : ',isDone)
      const listDoc = doc(todosCollectionRef, id)
      const editField = {
        isDone: !isDone
      }
      //console.log(editField.isDone)
      await updateDoc(listDoc,editField)
      setChanged(true)
    }

데이터의 기본 값은 false이며 Done 버튼을 누르면 true로 변환되면서 true일 때 css Style이 적용되도록 코드를 짰다.

값이 없을 때, 할 수 없을 때 등 부정에 관련된 값은 모두 false로 지정할 수 있고 값이 있다면, 할 수 있다면 등 긍정에 관련된 값은 모두 true로 지정할 수 있다.

전체코드

import React, { useEffect,useState } from 'react'
import { collection,getDocs,addDoc, doc, query,orderBy, updateDoc, deleteDoc } from 'firebase/firestore'
import { db } from './firebase'
import './App.css'
export default function App() {
  const [todos, setTodos] = useState([])
  const [newList, setNewList] = useState('')
  const [changed, setChanged] = useState(false)

  const todosCollectionRef = collection(db,"react-todos") //첫번째 인자는 index.js에 담은 상수 db, 두번째 인자는 컬렉션 이름 "todos"
  
  //read
  //데이터를 다 불러오면 실행할 수 있도록 함수를 비동기처리. async await
  useEffect(()=>{
    const getList = async () => {
      const data = await getDocs(
      //todosCollectionRef
      query(todosCollectionRef, orderBy('timestamp'))
    )

      //console.log(data)
      /*data.forEach((doc)=>{
        //doc.data() is never undefined for query doc snapshots
        console.log(doc.id, "=>", doc.data())
      })*/

     setTodos(
        data.docs.map(doc=>(
        {...doc.data(), id:doc.id}
        )
      )
    )
      }
        
    //console.log(todos)
      getList()
      setChanged(false)
  },[changed])

  //add Date
  const date = new Date()
  const nowDate = date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate()
  //console.log(nowDate)


  //create
  const createList = async ()=>{
    await addDoc(todosCollectionRef, {
      content: newList,
       //isDone: false,
      d_date: nowDate,  //json파일의 장점. 유연하다!
      timestamp: date //또는 date
    })
    setNewList("");
    setChanged(true)
  }

  //update
  const updateList = async(id, content)=>{
    //console.log(id,'/',content)
    const msg = window.prompt("TO DO", content)

    if(msg){

      const listDoc = doc(todosCollectionRef,id)
      const editField = {
        content:msg,
        d_date:nowDate
      }

      await updateDoc(listDoc, editField)


    }
    setChanged(true)

  }

    //delete
    const deleteList = async(id)=>{
      const isTrue = window.confirm("삭제하시겠습니까?")

      if(isTrue){
        const listDoc = doc(todosCollectionRef, id)
        await deleteDoc(listDoc)
      }
      setChanged(true)

    }

    //isDone
    const isDoneList = async (id,isDone)=>{
      //console.log(id,'/',isDone)
      //console.log('isDOne : ',isDone)
      const listDoc = doc(todosCollectionRef, id)
      const editField = {
        isDone: !isDone
      }
      //console.log(editField.isDone)
      await updateDoc(listDoc,editField)
      setChanged(true)
    }

  
  return (
    <div>
      <h2>오늘 할 일</h2>
      
        <hr/>
        <p>
        <input
        type='text'
        placeholder='to do'
        value={newList}
        onChange={e=>{
          setNewList(e.target.value)
        }}
        />
        <button
        onClick={createList}
        >ADD</button>
      </p>
      {
        todos.map(
          value=>(
            //console.log(value)
            <h3 key={value.id}>
              <span className={value.isDone?'complete':''}>{value.content}</span>
              <span className="date">{value.d_date}</span>
              <button 
                className={value.isDone? 'btn_complete' : ''}
                onClick={()=>{isDoneList(value.id, value.isDone)}
              }>DONE</button>

              <button onClick={()=>{updateList(value.id, value.content)}}>EDIT</button>

              <button onClick={()=>{deleteList(value.id)}}>DELETE</button>
            </h3>

          ))
      }
      
    </div>
  )
}

배포하기

npm run build

빌드된 파일을 미리보기 하고싶으면 npm install -g serve 해서 빌드된 파일을 미리보기 할 수 있는 모듈을 설치할 수 있다.

serve build

모듈을 설치한 뒤 명령어를 입력해서 빌드된 파일들을 미리보기 할 수 있다.

firebase login

미리 로그인을 해두었다면 skip!

파이어베이스 연결작업하기

firebase init

파이어베이스 초기화 작업이지만 연결작업이라고 생각하면 된다.

  1. Are you ready to proceed? yes
  2. Hosting 종류 2개가 있는데 gitHub Action 아니고 FirebaseHosting을 선택!
  3. Please select an option:(Use arrow keys) > Use an existing project 선택.(미리 프로젝트를 만들어두었으므로!)
  4. react-project 프로젝트 선택!
  5. what do you want to use as your public directory? > build
  6. 그 이후 질문들은 No 처리!

여기까지 하면 초기화가 끝난다.
여기에서 push작업을 해주면 된다.

firebase deploy

이후 나오는 주소를 클릭하면 실제 웹상에서 동작하는 앱을 만들 수 있다!

profile
오늘의 공부 기록📝

0개의 댓글