원문
보고 따라한 링크:
https://blog.logrocket.com/getting-started-react-redux-firebase/
다양한 글들도 있겟지만 속성으로 아무사람이나 따라하자고 생각해서 찾은 블로그지만 나쁘지 않은거 같아서 위 블로그 보고 정리 한 글임을 알려드립니다.
npx create-react-app firebase-todo-app1
npm install --save react-redux-firebase firebase
npm install --save redux react-redux
npm install --save redux-firestore@latest
npm install react-router-dom
이번 튜토리얼 사용할 기능들:
홈페이지: https://firebase.google.com/
- 홈페이지에 들어가서 시작하기 누르고 프로젝트 생성 따라한다.
- 프로젝트 생성 된 후 프로젝트 파일에 들어가서 DEVELOP => DATABASE에 들어간다.
Database 만들기
Create Database 클릭
test mode 로 데이터 실행하기 클릭 그럼 모달(팝업)이 뜬다.
데이터 베이스 서버를 선택하라고 하는데.. 지금 나는 한국이니까 아시아를 하는게 맞지 않을까? 해서 찾아보았더니
홍콩이나 도쿄로 하면될거같다는 생각이든다.
Authentication설정하기
Develop 에 Authentication 다시 클릭
Set up Sign-in method 클릭
다양한 인증인가 가 있는데 이번 프로젝트는 구글 로그인으로 기준 잡아서 실험 한다.
toogle 을 enable 로 변경하고, 저장을 누른다..(email까지 적어야 저장이 눌러진다)
앱과 Firebase Cloud 를 연동하기 위해서는 API key 등 프로젝트와 연동할 설정 세팅 정보가 필요하다.
1. Project Overview에 가서 연동할 앱을 설정하라고 나온다.
2. 프로젝트는 web 이니까 web을 선택해서 앱 이름을 설정한다.
- 본인은 firebase 를 처음 사용하기도 하고..firebase로 배포? 까지해서 hosting 해볼까해서 호스팅 동이하기를 눌렀고.. 진행하다가 firebase CLI 를 설치하라고 해서
npm install -g firebase-tools
까지 일단 설치를 했다
4. 배포 할거면 절차? 같은거 같은데 일단 보류하고 다음으로 넘어가기 클릭
- Project overview 옆에 톱니바퀴(설정)에 들어가서 General 밑에 firebase SDK snippet 창이 있는데 거기에 config 를 눌른다.
- 대강 이런 정보들이 생김
const firebaseConfig = { apiKey: "AXXXXXXXXXXXXXXXXXXXXXXXXXXX", authDomain: "test-XXXXX.firebaseapp.com", databaseURL: "https://test-XXXXX.firebaseio.com", projectId: "test-XXXXXX", storageBucket: "test-XXXXX.appspot.com", messagingSenderId: "XXXXXXXXX", appId: "XXXXXXXXXXXXXXXXXXXXXXXXX", measurementId: "XXXXXXXX" };
- index.js 파일에
import firebase from "firebase/app"; import "firebase/auth"; import "firebase/firestore"; const firebaseConfig = { apiKey: "AXXXXXXXXXXXXXXXXXXXXXXXXXXX", authDomain: "test-XXXXX.firebaseapp.com", databaseURL: "https://test-XXXXX.firebaseio.com", projectId: "test-XXXXXX", storageBucket: "test-XXXXX.appspot.com", messagingSenderId: "XXXXXXXXX", appId: "XXXXXXXXXXXXXXXXXXXXXXXXX", measurementId: "XXXXXXXX" }; firebase.initializeApp(firebaseConfig); firebase.firestore();
내용들을 추가하여 연동시킨다.
보통은 이런것들을 env나 config로 따로 설정하고 비공개로 한다고 알고있다.. 이건 튜토리얼이니까 그래로 놔두겟다.
원문 링크 스타일로 진행하겠다. 일단 해보는게 중요하니까..
이 저자가 말하기론 reducer.js에 모든 코드를 쓰겟다고 한다..
import {combineReducers} from "redux";
import {firebaseReducer} from "react-redux-firebase";
import {firestoreReducer} from "redux-firestore";
export const rootReducer = combineReducers({
firebase: firebaseReducer,
firestore: firestoreReducer
});
Firebase 에서 제공하는 리액트 리덕스 리듀서들도 불러온다.
const rrfConfig = {
userProfile: "users",
useFirestoreForProfile: true,
};
우리 데이터 저장공간이름을 우리가 설정할 수 있는데 여기선 "users" 라고 지었고, 파이어 스토어 프로파일에 접근해야 하기때문에 true 로 설
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";
import { createStore, compose } from "redux";
import { Provider } from "react-redux";
import { ReactReduxFirebaseProvider } from "react-redux-firebase";
import { createFirestoreInstance } from "redux-firestore";
import { rootReducer } from "./ducks/reducers";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import * as serviceWorker from "./serviceWorker";
const firebaseConfig = {
apiKey: "AIzaSyBJTFQkg95Aj-s_NsA77mco8ZVG2siLv4U",
authDomain: "react-redux-firebase-article.firebaseapp.com",
databaseURL: "https://react-redux-firebase-article.firebaseio.com",
projectId: "react-redux-firebase-article",
storageBucket: "react-redux-firebase-article.appspot.com",
messagingSenderId: "781345165856",
appId: "1:781345165856:web:45fd42a60e5bb365172245",
measurementId: "G-XFR3YXLCGW",
};
const rrfConfig = {
userProfile: "users",
useFirestoreForProfile: true,
};
firebase.initializeApp(firebaseConfig);
firebase.firestore();
const initialState = {};
const store = createStore(rootReducer, initialState);
const rrfProps = {
firebase,
config: rrfConfig,
dispatch: store.dispatch,
createFirestoreInstance,
};
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<ReactReduxFirebaseProvider {...rrfProps}>
<BrowserRouter>
<App />
</BrowserRouter>
</ReactReduxFirebaseProvider>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
위 사람 코드는 index.js에 모두 다 넣었지만 원래 분리하는게 맞고. 숨길것들은 숨기는게 맞다.
이사람 프로젝트에는 아래 component 와 기능들이 구현되있다.
AddTodo: todos 추가
PrivateRoute: 인증되지 않은 사용자들이 생성하는 component들은 접근권한 없게 함.
TodoItem: todos 기능들 및 UI 구현 파일들
SignIn: Google 로그인
Todos: 인증된 TODOS 리스트 나열
HOOKS:
Firebase Hook:
- useFirebase Hooks 사용하여 database에 데이타를 전달,로그인 기능들 구현
React-router-hooks:
- useHistory 사용하여 todos route로 연결
import React from "react";
import { useFirebase } from "react-redux-firebase";
import { useHistory } from "react-router-dom";
const SignIn = () => {
const firebase = useFirebase();
const history = useHistory();
const signInWithGoogle = () => {
firebase
.login({
provider: "google",
type: "popup",
})
.then(() => {
history.push("/todos");
});
};
return (
<div>
<h1>Sign In</h1>
<button
onClick={(event) => {
event.preventDefault();
signInWithGoogle();
}}
>
Sign In with Google
</button>
</div>
);
};
export default SignIn;
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { isLoaded, isEmpty } from "react-redux-firebase";
import { useSelector } from "react-redux";
const PrivateRoute = ({ children, ...remainingProps }) => {
const auth = useSelector(state => state.firebase.auth);
return (
<Route
{...remainingProps}
render={({ location }) =>
isLoaded(auth) && !isEmpty(auth) ? (
children
) : (
<Redirect
to={{
pathname: "/",
state: { from: location },
}}
/>
)
}
/>
);
};
export default PrivateRoute;
모든 Todo item 안에는:
isDone – 완료 여부
title – 제목
todoID – 고유 ID
위 내용들이 들어가 있음
import React, { useState } from "react";
import { useFirestore } from "react-redux-firebase";
import { useSelector } from "react-redux";
const AddTodo = () => {
const [presentToDo, setPresentToDo] = useState("");
const firestore = useFirestore();
const { uid } = useSelector((state) => state.firebase.auth);
const handleChange = ({ currentTarget: { name, value } }) => {
if (name === "addTodo") {
setPresentToDo(value);
}
};
const addNewTodo = (todo) => {
firestore
.collection("users")
.doc(uid)
.collection("todos")
.add({
title: todo,
isDone: false,
})
.then((docRef) => {
docRef.update({
todoID: docRef.id,
});
});
setPresentToDo("");
};
return (
<div>
<form action="">
<input
type="text"
name="addTodo"
value={presentToDo}
onChange={handleChange}
/>
<button
onClick={(event) => {
event.preventDefault();
addNewTodo(presentToDo);
}}
>
Add Todo
</button>
</form>
</div>
);
};
export default AddTodo;
사용자가 checkbox 를 클릭하면 Cloud firestore 의 데이터를 업데이트 하여 보여줌
import React, { useState } from "react"
import { useFirestore } from "react-redux-firebase"
import { useSelector } from "react-redux"
const ToDoItem = ({ isDone, title, todoID }) => {
const [isTodoItemDone, setTodoItemDone] = useState(isDone)
const firestore = useFirestore()
const { uid } = useSelector((state) => state.firebase.auth)
console.log(isTodoItemDone)
const handleChange = (event) => {
if (event.currentTarget.type === "checkbox") {
setTodoItemDone(!isTodoItemDone)
firestore.collection("users").doc(uid).collection("todos").doc(todoID).update({
isDone: !isTodoItemDone,
})
}
}
return (
<div
style={{
textDecoration: isTodoItemDone && "line-through",
opacity: isTodoItemDone ? 0.5 : 1,
}}
>
<input type="checkbox" name="" id="" onChange={handleChange} checked={isTodoItemDone} />
{title}
</div>
)
}
export default ToDoItem
import React from "react";
import { useSelector } from "react-redux";
import AddTodo from "../Components/AddTodo";
import { useFirestoreConnect } from "react-redux-firebase";
import ToDoItem from "../Components/TodoItem";
const Todos = () => {
const { displayName, uid } = useSelector((state) => state.firebase.auth);
useFirestoreConnect({
collection: `users/${uid}/todos`,
storeAs: "todos",
});
const todos = useSelector((state) => state.firestore.data.todos);
console.log(todos);
return (
<div>
<h3>Hello {displayName}</h3>
<h4>Todos</h4>
<AddTodo />
<ul
style={{
listStyleType: "none",
}}
>
{todos &&
Object.values(todos).map((todo) => (
<li>
<ToDoItem
title={todo.title}
isDone={todo.isDone}
todoID={todo.todoID}
/>
</li>
))}
</ul>
</div>
);
};
export default Todos;
import React from 'react';
import PrivateRoute from "./UI/Components/PrivateRoute";
import Todos from "./UI/Routes/Todos";
import SignIn from "./UI/Routes/SignIn";
import {Switch, Route} from "react-router-dom";
function App() {
return (
<div style = {{
textAlign: "center"
}}>
<h1>Redux Todo App</h1>
<Switch>
<PrivateRoute path = "/todos">
<Todos />
</PrivateRoute>
<Route path = "/">
<SignIn />
</Route>
</Switch>
</div>
);
}
export default App;