index.css
body {
margin: 40px 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #f2f2f2;
}
.App {
max-width: 640px;
margin: 0 auto;
}
nav {
background: #772eff;
padding: 10px 20px;
color: #fff;
display: flex;
align-items: center;
}
nav h1 {
margin: 0;
margin-right: auto;
}
nav li {
display: inline-block;
margin: 0 0 0 16px;
cursor: pointer;
}
nav a {
color: #fff;
text-decoration: none;
}
.todo-list ul {
padding: 0;
}
.todo-list li {
list-style-type: none;
padding: 10px;
background: #fff;
margin: 10px 0;
cursor: pointer;
}
label,
label span {
display: block;
margin: 6px 0;
}
input {
padding: 6px;
margin-bottom: 12px;
}
button {
background: #772eff;
color: #fff;
border: 0;
padding: 6px 8px;
}
index.css교체하고 파일 정리함
컴포넌트 파일 js-> jsx로 변경해야함
리액트 라우터 설치하기
.
App.jsx 에서 라우트 사용및 네브바 컴포넌트도 사용한다.
각 항목을 클릭하면 console에 id값 출력되게 되어있음
파이어 베이스 새 프로젝트 생성
완료되면 웹 클릭
앱 등록 후 콘솔로 이동
설정 아이콘으로 API키 확인 가능함
구성 클릭해서 나오는 아래의 내용들이 중요함
그 부분을 전체 복사하여 사용
그리고 터미널로 파이어 베이스 설치한다. 이번에느 8.5버전 아니고 최신버전 설치
firebase폴더 생성 후 config.js 파일 생성
firebaseConfig에 복사한 파이어 베이스 객체 붙여넣기
import { initializeApp } from 'firebase/app';
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
// 붙여넣기
};
// init firebase
initializeApp(firebaseConfig);
// init firestore
const db = getFirestore();
export { db };
그리고 이 KEY들은 공유되면 안되는 키라서
.env파일로 만들어서 보관할거임
9버전은 라이브러리에서 가져온 함수로 초기화 하면됨
그리고 데이터 베이스를 만든다.
테스트 모드에서 시작
컬렉션 샘플로 만들어봄
문서 id는 자동생성
초기 값 다 지움
Home.jsx
import { useEffect, useState } from "react";
import TodoList from "../components/TodoList.jsx";
import TodoForm from "../components/TodoForm.jsx";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase/config.js";
export default function Home() {
const [todos, setTodos] = useState(null);
useEffect(() => {
const ref = collection(db, "todos"); //firebase config의 db임, todos가져오기
//todos컬렉션에 모든 문서들을 가져오기
getDocs(ref).then((snapshot) => {
let results = [];
//성공 시 .docs의 모든 문서들이 들어있음
snapshot.docs.forEach((doc) => {
results.push({ id: doc.id, ...doc.data() });
});
setTodos(results);
});
}, []); //한 번만 실행
return (
<div className="App">
{todos && <TodoList todos={todos} />}
<TodoForm />
</div>
);
}
todos에서 가져온 데이터가 출력되는것을 확인가능하다.
새로운 문서를 추가해서 테스트 해본다.
하지만 현재는 리얼타임으로 바로 업데이트가 되지 않기 때문에 한번 새로고침 해줘야 확인 가능함
콘솔로 현재 todos에 어떤 값들이 들어있는지 확인해봄
위의 문서 가져오는것을 리얼타임으로 변경해봤다.
import { useEffect, useState } from "react";
import TodoList from "../components/TodoList.jsx";
import TodoForm from "../components/TodoForm.jsx";
import { collection, getDocs, onSnapshot } from "firebase/firestore";
import { db } from "../firebase/config.js";
export default function Home() {
const [todos, setTodos] = useState(null);
//console.log(todos);
useEffect(() => {
const ref = collection(db, "todos"); //firebase config의 db임, todos가져오기
//todos컬렉션에 모든 문서들을 가져오기
const unsub = onSnapshot(ref, (snapshot) => {
let results = [];
//성공 시 .docs의 모든 문서들이 들어있음
snapshot.docs.forEach((doc) => {
results.push({ id: doc.id, ...doc.data() });
});
setTodos(results);
});
return () => unsub();
}, []); //한 번만 실행
return (
<div className="App">
{todos && <TodoList todos={todos} />}
<TodoForm />
</div>
);
}
새 할일 추가하기
9버전 부터는 필요한 함수를 바로 바로 가져올 수 있음
TodoForm.jsx
import { addDoc, collection } from "firebase/firestore";
import { useState } from "react";
export default function TodoForm() {
const [newTodo, setNewTodo] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const ref = collection(db, "todos");
await addDoc(ref, {
title: newTodo, //입력할 내용을 객체형식으로 넣으면됨
}); //추가할 때 까지 대기 await
setNewTodo("");
};
return (
<form onSubmit={handleSubmit}>
<label>
<span>새 할일 :</span>
<input
required
type="text"
onChange={(e) => setNewTodo(e.target.value)}
value={newTodo}
/>
</label>
<button>추가</button>
</form>
);
}
그리고 삭제하기 기능도 넣어보자
https://firebase.google.com/docs/firestore/manage-data/delete-data?hl=ko
import { deleteDoc, doc } from "firebase/firestore";
import { db } from "../firebase/config";
export default function TodoList({ todos }) {
const handleClick = async (id) => {
const ref = doc(db, "todos", id); //db config 추가하기
await deleteDoc(ref);
};
return (
<div className="todo-list">
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => handleClick(todo.id)}>
{todo.title}
</li>
))}
</ul>
</div>
);
}
인증하기 기능 추가한다.
\https://firebase.google.com/docs/auth/web/start?hl=ko
config.js에 인증 추가
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: import.meta.env.VITE_API_KEY,
authDomain: import.meta.env.VITE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_PROJECTID,
storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_MESSAGEING_SENDERID,
appId: import.meta.env.VITE_APPID,
};
// 파이어베이스 초기설정
initializeApp(firebaseConfig);
//DB초기화
const db = getFirestore();
//인증
const auth = getAuth();
export { db, auth };
Signup.jsx
import { createUserWithEmailAndPassword } from "firebase/auth";
import { useState } from "react";
import { auth } from "../firebase/config";
export default function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(null); //에러
const handleSubmit = (e) => {
e.preventDefault();
setError(null); // 처음에는 에러 x
createUserWithEmailAndPassword(auth, email, password) //
.then((res) => {
console.log("유저가입 : ", res.user);
})
.catch((err) => {
setError(err.message); //에러가 있으면 에러메세지를 state에 저장
});
};
promise는 성공했을 때
catch는 실패했을 때
에러메시지 하단에 추가
</label>
<button>가입하기</button>
{error && <p>{error}</p>}
</form>
</div>
);
}
에러 발생
로그아웃 추가
Navbar.jsx 로그아웃 버튼 클릭 시 로그아웃 함수 실행
import { signOut } from "firebase/auth";
import React from "react";
import { Link } from "react-router-dom";
import { auth } from "../firebase/config";
export default function Navbar() {
const logout = () => {
signOut(auth)
.then(() => {
console.log("유저 로그아웃");
})
.catch((err) => {
console.log(err.message);
});
};
return (
<nav>
<h1>My Todo List</h1>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/signup">Signup</Link>
</li>
<li onClick={logout}>Logout</li>
</ul>
</nav>
);
}
로그인
Login.jsx
import { signInWithEmailAndPassword } from "firebase/auth";
import { useState } from "react";
import { auth } from "../firebase/config";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(null);
const handleSubmit = (e) => {
e.preventDefault();
setError(null);
signInWithEmailAndPassword(auth, email, password)
.then((res) => {
console.log("유저로그인 : ", res.user);
})
.catch((err) => {
setError(err.message);
});
};
하단에 에러 메세지 출력하도록 p태그 추가
</label>
<button>log in</button>
{error && <p>{error}</p>}
글로벌 스테이트 Auth 컨텍스트 추가하여 가입/로그인 , 로그아웃 작업시 리듀서로 글로벌스테이트 업데이트
수정해야할것 .
AuthContext는 js-> jsx로 수정
AuthContext와 같은 폴더에 있으므로 주소 수정
main.jsx
Signup.jsx
import { createUserWithEmailAndPassword } from "firebase/auth";
import { useState } from "react";
import { auth } from "../firebase/config";
import { useAuthContext } from "../context/useAuthContext";
export default function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(null); //에러
const { dispatch } = useAuthContext();
const handleSubmit = (e) => {
e.preventDefault();
setError(null); // 처음에는 에러 x
createUserWithEmailAndPassword(auth, email, password) //auth인증객체
.then((res) => {
//가입 성공 후 자동로그인 되므로 로그인 상태 업데이트
dispatch({ type: "LOGIN", payload: res.user });//리듀서 업데이트
})
.catch((err) => {
setError(err.message); //에러가 있으면 에러메세지를 state에 저장
});
};
Login.jsx
import { signInWithEmailAndPassword } from "firebase/auth";
import { useState } from "react";
import { auth } from "../firebase/config";
import { useAuthContext } from "../context/useAuthContext";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(null);
const { dispatch } = useAuthContext();
const handleSubmit = (e) => {
e.preventDefault();
setError(null);
signInWithEmailAndPassword(auth, email, password)
.then((res) => {
//로그인 성공했으므로 유저 상태 업데이트
dispatch({ type: "LOGIN", payload: res.user }); //리듀서 업데이트
})
.catch((err) => {
setError(err.message);
});
};
Navbar.jsx
import { signOut } from "firebase/auth";
import React from "react";
import { Link } from "react-router-dom";
import { auth } from "../firebase/config";
import { useAuthContext } from "../context/useAuthContext";
export default function Navbar() {
const { dispatch } = useAuthContext();
const logout = () => {
signOut(auth)
.then(() => {
dispatch({ type: "LOGOUT" });
})
.catch((err) => {
console.log(err.message);
});
};
type은 AuthContext에 설정되어있는 값임
1.로그인 성공 시
2. 로그아웃 성공 시
네비게이션 가드 추가
App.jsx
AuthContext에서user 불러와서
유저가 있을 경우에는 Home으로 이동 만약에 로그인 되어있지 않으면 (user -> null)
login페이지로 이동
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import "./App.css";
import Home from "./pages/Home.jsx";
import Signup from "./pages/Signup.jsx";
import Login from "./pages/Login.jsx";
import Navbar from "./components/Navbar.jsx";
import { useAuthContext } from "./context/useAuthContext.js";
function App() {
const { user, authIsReady } = useAuthContext();
return (
<div className="App">
{authIsReady && (
<BrowserRouter>
<Navbar />
<Routes>
<Route
path="/"
element={user ? <Home /> : <Navigate to="/login" />}
/>
<Route path="/signup" element={!user ? <Signup /> : <Home />} />
<Route path="/login" element={!user ? <Login /> : <Home />} />
</Routes>
</BrowserRouter>
)}
</div>
);
}
export default App;
Navbar.jsx
export default function Navbar() {
const { dispatch, user } = useAuthContext();
<nav>
<h1>My Todo List</h1>
<p></p>
<ul>
{user && (
<>
<li> 👋{user.email} 님</li>
</>
)}
<li>
<Link to="/">Home</Link>
</li>
{!user && (
<>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/signup">Signup</Link>
</li>
</>
)}
{user && (
<>
<li onClick={logout}>Logout</li>
</>
)}
</ul>
</nav>
로그인 되어있으면 로그아웃 출력됨. -> 로그아웃 클릭 시 로그인 페이지로 이동
로그아웃 되어있으면 로그아웃 버튼 출력되지 않음
-
유저별로 할일 만들기
TodoForm.jsx
useAuthContex에서 user정보 가져와서 addDoc할 때 user의 uid 추가 입력함
import { addDoc, collection } from "firebase/firestore";
import { useState } from "react";
import { db } from "../firebase/config";
import { useAuthContext } from "../context/useAuthContext";
export default function TodoForm() {
const [newTodo, setNewTodo] = useState("");
const { user } = useAuthContext();
const handleSubmit = async (e) => {
e.preventDefault();
const ref = collection(db, "todos"); //db config 추가하기
await addDoc(ref, {
title: newTodo, //입력할 내용을 객체형식으로 넣으면됨
uid: user.uid,
}); //추가할 때 까지 대기 await
setNewTodo("");
};
견과류 먹기를 추가해봤다.
정상적으로 추가된것이 확인되고,
파이어 베이스에도 uid가 잘 입력된것을 확인 할 수 있다.
그리고 마지막으로
https://firebase.google.com/docs/firestore/query-data/queries?hl=ko
로그인 한 유저가 한 일만을 출력해야하니까
쿼리를 추가하여 가져온다.
import { useEffect, useState } from "react";
import TodoList from "../components/TodoList.jsx";
import TodoForm from "../components/TodoForm.jsx";
import {
collection,
getDocs,
onSnapshot,
query,
where,
} from "firebase/firestore";
import { db } from "../firebase/config.js";
import { useAuthContext } from "../context/useAuthContext.js";
export default function Home() {
const [todos, setTodos] = useState(null);
//console.log(todos);
const { user } = useAuthContext(); //유저 정보 가져옴
useEffect(() => {
const ref = collection(db, "todos"); //firebase config의 db임, todos가져오기
const q = query(ref, where("uid", "==", user.uid));
//todos컬렉션에 모든 문서들을 가져오기
const unsub = onSnapshot(q, (snapshot) => {
let results = [];
//성공 시 .docs의 모든 문서들이 들어있음
snapshot.docs.forEach((doc) => {
results.push({ id: doc.id, ...doc.data() });
});
setTodos(results);
});
return () => unsub();
}, []); //한 번만 실행
return (
<div className="App">
{todos && <TodoList todos={todos} />}
<TodoForm />
</div>
);
}
susu로 로그인 했을 때는 susu 유저의 할일만 뜸
다른 아이디로 로그인해봤다.