Firebase - Realtime Databse를 이용해 DB 구현하기 (Feat. React.js)

Noma·2021년 5월 6일
1
post-thumbnail

✅ 이번에는 Firebase로 실시간 데이터베이스를 구축해보겠습니다. (지난 게시물과 이어집니다.)

1. 데이터베이스 생성

먼저 데이터베이스를 테스트 모드로 빠르게 생성해준다.

Firebase > Console > Build > Realtime Database > Create Database 클릭

정상적으로 만들어졌으면 아래와 같이 데이터베이스 url을 얻을 수 있다.

이 URL은 firebaseConfig에 들어갈 정보로, config로 firebase를 initialize 해줌으로써 데이터베이스를 이용할 수 있게 된다.

📍 firebase.js

//src/service/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';//🌈추가

const firebaseConfig={
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_FIREBASE_DB_URL,
    projectId: process.env.REACT_APP_PROJECT_ID,
};
const firebaseApp=firebase.initializeApp(firebaseConfig);

export const firebaseAuth=firebaseApp.auth();
export const firebaseDB=firebaseApp.database(); //🌈추가
export const googleProvider=new firebase.auth.GoogleAuthProvider();
export const githubProvider=new firebase.auth.GithubAuthProvider();

2. 전용 클래스 만들기

이후 데이터를 실시간으로 읽고, 쓰고, 삭제하는 로직을 담을 js파일을 service폴더에 아래와 같이 생성해준다.

데이터 쓰기, 읽기, 삭제 기능은 링크된 공식 사이트에 기재된 방법을 이용해 만들자.

간단히 정리하면 다음과 같다.

  • 데이터 저장: set() 메서드에 저장할 데이터를 인자로 전달해 호출
  • 데이터 삭제: remove() 메서드 호출
  • 데이터 읽기 & 변경사항 수신: ref에 on() 또는 once() 메서드를 사용하여 이벤트(value)를 관찰

리스너는 이벤트 발생 시점에 데이터베이스에서 지정된 위치(ref)에 있던 데이터를 포함하는 snapshot을 수신한다.

어떤 데이터를 가지고 있었는지 알고 싶다면, snapshotval() 메서드를 사용하면 접근 가능하다.

📍 repository.js

//src/service/repository.js
import { firebaseDB } from './firebase';

class Repository {
    storeInfo(userId, info) {
        firebaseDB.ref(`info/${userId}/${info.id}`).set(info);
    }

    deleteInfo(userId, info) {
        firebaseDB.ref(`info/${userId}/${info.id}`).remove();
    }
    readInfo(userId, onUpdate) {
        const dbRef = firebaseDB.ref(`info/${userId}`);
        dbRef.on('value', snapshot => {
            const data = snapshot.val();
            data && onUpdate(data);
        })
        return dbRef.off;
    }
}
export default Repository;

ref()안의 주소는 자신이 원하는 경로로 지정하면 된다. 그러면 그 경로에 데이터를 저장하거나 삭제하는 등의 작업이 이루어진다.

이렇게 만든 클래스를 최상위 index.js에서 import해서 만들어 준 뒤 데이터를 저장할 컴포넌트까지 props로 전달해준다. (DI)

📍 index.js

//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import Auth from './service/auth';
import Repository from './service/repository';

const auth = new Auth();
const repository = new Repository();

ReactDOM.render(
  <React.StrictMode>
    <App auth={auth} repository={repository} />
  </React.StrictMode>,
  document.getElementById('root')
);

📍 app.jsx

//src/app.jsx
import React from 'react';
import Login from './components/login';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Contact from './components/contact';

function App({ auth, repository }) {
  return <div>
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Login auth={auth} />
        </Route>
        <Route path="/contact">
          <Contact
            auth={auth}
            repository={repository} //🌈전달해주기
          />
        </Route>
      </Switch>
    </BrowserRouter>
  </div>;
}

export default App;

3. 정의한 함수로 데이터베이스 기능 이용하기

그런 다음 연락처를 실시간으로 저장하고, 수정할 수 있는 컴포넌트를 만들어 주자.

위에서 데이터베이스에 정보를 조작할 수 있는 함수들을 repository 파일에 클래스로 만들어 주었고, index에서 생성해 이 컴포넌트로 전달해 주었다.

그러므로 여기서 해당 함수들을 이용해 다음 기능을 구현해주자.

  • ① userId가 있다면(즉 로그인된 기록이 있다면) 데이터베이스에서 해당 id로 저장되었던 데이터들을 불러온다.
  • ② Edit 컴포넌트에서 변경된 데이터를 Contact 컴포넌트의 state(여기선, infos)에 업데이트해주고, 데이터베이스에도 저장해준다.
  • ③ Edit 컴포넌트에서 삭제한 데이터를 infos에서 삭제해주고, 데이터베이스에서도 삭제해준다.
  • ④ Add 컴포넌트에서 추가된 데이터를 infos에 추가해주고, 데이터베이스에도 추가해준다.

📍 contact.jsx

// src/components/contact.jsx
import React, { useEffect, useCallback, useState } from 'react';
import { useHistory } from 'react-router';
import Add from './add';
import Edit from './edit';
import Header from './header';

const Contact = ({ auth, repository }) => {
    const [infos, setInfos] = useState({});

    const history = useHistory();
    const state = history.location.state; //🌈추가
    const [userId, setUserId] = useState(state && state.id);

    const onLogout = useCallback(() => {
        auth.logout();
    }, [auth]);

    //🌈 2,4번 기능 구현
    const updateInfo = info => {
        setInfos(infos => {
            const updated = { ...infos };
            updated[info.id] = info;
            return updated;
        });
        repository.storeInfo(userId, info);
    };

    //🌈 3번 기능 구현
    const deleteInfo = info => {//🌈추가된 부분
        setInfos(infos => {
            const updated = { ...infos };
            delete updated[info.id];
            return updated;
        });
        repository.deleteInfo(userId, info);
    };

    useEffect(() => {//🌞변경된 부분
        //onAuthChange는 로그인 상태가 바뀌면 user 정보를 받아오고,
        //등록된 콜백함수에 user를 인자로 전달해 실행해준다.
        auth.onAuthChange(user => {
            if (user) {
                setUserId(user.uid);
            } else {
                history.push('/');
            }
        });
    }, [auth, history]);

    //🌈 1번 기능 구현
    useEffect(() => {
        if (!userId) {
            return;
        }
        //userId가 있다면(즉 로그인된 기록이 있다면) 데이터베이스에서
        //해당 id로 저장되었던 데이터들을 불러온다.
        const stopRead = repository.readInfo(userId, value => {
            setInfos(value);
        });
        //함수를 호출하고 그 리턴값(리스너를 제거해주는 함수)을
        //stopRead에 할당 받아 useEffect의 cleanup 함수로 등록해준다.
        return stopRead;
    }, [userId, repository]);

    return (
        <section>
            <Header onLogout={onLogout} />
            {Object.keys(infos).map(key => {
                return <Edit
                    info={infos[key]}
                    updateInfo={updateInfo}
                    deleteInfo={deleteInfo}
                />;
            })}
            <Add
                updateInfo={updateInfo}
            />
        </section>
    );

};
export default Contact;

마지막으로 Contact의 하위 컴포넌트인 Edit과 Add를 아래와 같이 만들어 주면 끝이다.

📍 edit.jsx

//src/components/edit.jsx
import React from 'react';

const Edit = ({ info, updateInfo, deleteInfo }) => {
    const { name, phone } = info;

    const onChange = (e) => {
        if (e.target == null) {
            return;
        }
        e.preventDefault();
        updateInfo({
            ...info,
            [e.target.name]: e.target.value,
        });
    };

    const onSubmit = () => {
        deleteInfo(info);
    };

    return (
        <form >
            <input
                type="text"
                name="name"
                value={name}
                onChange={onChange}
            />
            <input
                type="text"
                name="phone"
                value={phone}
                onChange={onChange}
            />
            <button onClick={onSubmit}>삭제</button>
        </form>
    );
};

export default Edit;

📍 add.jsx

//src/components/add.jsx
import React, { useRef } from 'react';

const Add = ({ updateInfo }) => {
    const formRef = useRef();
    const nameRef = useRef();
    const phoneRef = useRef();

    const onAdd = (e) => {
        e.preventDefault();
        const info = {
            id: Date.now(), //uuid
            name: nameRef.current.value || '',
            phone: phoneRef.current.value || ''
        }
        updateInfo(info);
        formRef.current.reset();
    };

    return (
        <form ref={formRef}>
            <input
                ref={nameRef}
                type="text"
                name="name"
                autocomplete="off"
            /> //자동완성을 꺼줍니다.
            <input
                ref={phoneRef}
                type="text"
                name="phone"
                autocomplete="off"
            />
            <button onClick={onAdd}>저장</button>
        </form>
    );
};

export default Add;

Login과 Header 컴포넌트는 이전 포스팅에서 구현된 내용과 변화가 없으므로, 그대로 사용하면 됩니다.

이렇게 만들면, 처음 목표로 했던 기능들이 잘 작동하는 것을 확인할 수 있습니다.

🔍 결과물 미리보기

❗ 로그인 팝업에 개인 이메일 정보가 노출되어 팝업창은 옆으로 치워 안보이게 했습니다.

(로그인시 로딩시간이 좀 걸립니다.)

  • 로그인해서 Contact 화면으로 이동한다.

  • 정보 입력후 저장 또는 삭제가 가능하다.

  • 새로고침 시 이전 로그인 정보로 저장했던 데이터 불러온다.

  • 로그아웃하고 다시 들어가면 해당 로그인 uid에 따라 저장된 데이터가 있으면 보여준다.

📝 Reference

https://firebase.google.com/docs/database/web/read-and-write?authuser=0

profile
Frontend Web/App Engineer

0개의 댓글