๐ŸŒŸ ์ฟผ๋ฆฌ ํ•„ํ„ฐ๋ง: ํ”„๋กœํ•„ ํ™”๋ฉด์— ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ

summereuna๐Ÿฅยท2022๋…„ 5์›” 24์ผ

๐ŸŒŸ Twinkle (React, Firebase)

๋ชฉ๋ก ๋ณด๊ธฐ
22/42

์ด์ œ ํ”„๋กœํ•„ ํ™”๋ฉด์„ ๋œฏ์–ด ๊ณ ์ณ๋ณด์ž!
ํ”„๋กœํ•„ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•  ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋กœ๊ทธ์•„์›ƒ โœ…
  • ํ”„๋กœํ•„ ํ™”๋ฉด์— ๋‚ด๊ฐ€ ์“ด ํŠธ์œ— ๊ฐ€์ ธ์˜ค๊ธฐ
  • ํ”„๋กœํ•„ ์—…๋ฐ์ดํŠธ

๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์€ ์ด๋ฏธ ํ•ด ๋’€๊ณ , ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํŠธ์œ—์„ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ•ด๋ณด์ž!


โœ… ํ”„๋กœํ•„ ํ™”๋ฉด์— ๋‚ด ํŠธ์œ— ๊ฐ€์ ธ์˜ค๊ธฐ

1. Router์—์„œ Profile๋กœ userObj ๋ณด๋‚ด๊ธฐ


๋ˆ„๊ตฌ์˜ ํ”„๋กœํ•„์ธ์ง€ ์•Œ์•„์•ผ ๊ทธ ์œ ์ €์˜ ํŠธ์œ—๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
Router.js์—์„œ Profile.js๋กœ `prop์œผ๋กœ userObj๋ฅผ ๋ณด๋‚ด์ฃผ๋ฉด ์ด ํ”„๋กœํ•„์˜ ์œ ์ €๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“src > components > Router.js

//...

<Route
  path="/profile"
  element={<Profile userObj={userObj} />}
  ></Route>

//...

๐Ÿ“src > routes > Profile.js

import { dbService } from "fbase";
import { useNavigate } from "react-router-dom";

//๐Ÿ”ฅ ๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด prop์œผ๋กœ ๋ฐ›๊ธฐ
const Profile = ({ userObj }) => {
  const navigate = useNavigate();
  const onLogOutClick = () => {
    authService.signOut();
    //home์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ์œ„ํ•ด react router dom์˜ useNavigate() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    navigate("/");
  };
  
  return (
    <>
      <button onClick={onLogOutClick}>๋กœ๊ทธ์•„์›ƒ</button>
    </>
  );

export default Profile;

2. useEffect() ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœํ•„ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ํ›„ ์‚ฌ์šฉ์ž ํŠธ์œ— ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ

  • useEffect( fn, [props.source] ): ์กฐ๊ฑด๋ถ€ effect ๋ฐœ์ƒ
    effect์˜ ๊ธฐ๋ณธ ๋™์ž‘์€ ๋ชจ๋“  ๋ Œ๋”๋ง์„ ์™„๋ฃŒํ•œ ํ›„ effect๋ฅผ ๋ฐœ์ƒํ•˜๊ฒŒ ํ•˜์ง€๋งŒ, useEffect์— ๋‘ ๋ฒˆ์งธ ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋ฉด props.source๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ์—๋งŒ ๊ตฌ๋…์ด ์žฌ์ƒ์„ฑ๋œ๋‹ค.
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

1. useEffect() ํ•จ์ˆ˜ ์ƒ์„ฑ

import { authService } from "fbase";
import { useEffect } from "react";
import <{ useNavigate } from "react-router-dom";

//๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด prop์œผ๋กœ ๋ฐ›๊ธฐ
const Profile = ({ userObj }) => {
  const navigate = useNavigate();
  const onLogOutClick = () => {
    authService.signOut();
    //home์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ์œ„ํ•ด react router dom์˜ useNavigate() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    navigate("/");
  };

  //๐Ÿ”ฅ ๋‚ด Tweets ์–ป๋Š” function ํ˜ธ์ถœ
  useEffect(() => {
    getMyTweets();
  }, []);

  return (
    <>
      <button onClick={onLogOutClick}>๋กœ๊ทธ์•„์›ƒ</button>
    </>
  );
};

export default Profile;

2. ๋‚ด Tweets ์–ป๋Š” function์ธ getMyTweets ํ•จ์ˆ˜ ์ƒ์„ฑํ•˜๊ธฐ

getMyTweets ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•จ์ˆ˜ ์•ˆ์— ๋‚ด Tweets์„ ์–ป๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ž.
์ฐธ๊ณ : ์ปฌ๋ ‰์…˜์—์„œ ์—ฌ๋Ÿฌ ๋ฌธ์„œ ๊ฐ€์ ธ์˜ค๊ธฐ

2.1. firebase/firestore์—์„œ collection, query, where, getDocs๋ฅผ import ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ปฌ๋ ‰์…˜์—์„œ ์—ฌ๋Ÿฌ ๋ฌธ์„œ(ํŠธ์œ—)๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ

import { collection, query, where, getDocs } from "firebase/firestore";

2.2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— userObj.uid์™€ ๊ฐ™์€ creatorId ๊ฐ€์ง„ ๋ชจ๋“  ๋ฌธ์„œ ์ฟผ๋ฆฌ(์š”์ฒญ)ํ•˜๊ธฐ

ํ˜„์žฌ ๋””๋น„์„œ๋น„์Šค ์•ˆ์— ์žˆ๋Š” ์ปฌ๋ ‰์…˜์—์„œ "tweets"๋ฌธ์„œ๋ฅผ ์ฟผ๋ฆฌํ•ด ์™€์•ผ ํ•œ๋‹ค. ๊ทธ์ค‘์—์„œ๋„ ํŠน๋ณ„ํžˆ userObj.uid์™€ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์ง„ creatorId๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฌธ์„œ๋งŒ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์•ผ ์ž๊ธฐ ํŠธ์œ—๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด where() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ, ์ด ๋ฉ”์„œ๋“œ๋Š” ํŠน์ • ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜๋Š” ๋ชจ๋“  ๋ฌธ์„œ๋ฅผ ์ฟผ๋ฆฌ(์š”์ฒญ)ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • where(filePath, opStr, value)
    • filePath: string | firebase.firestore.FiledPath
    • opStr("์˜คํผ๋ ˆ์ด์…˜ ์ŠคํŠธ๋ง" ์ฆ‰, ์—ฐ์‚ฐ์ž) | firebase.firestore.WhereFilterOp
    • value(๊ฐ’): any

๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

const q = query(
  collection(dbService, "tweets"),
  where("creatorId", "==", userObj.uid)
);
  • ์ฒซ ๋ฒˆ์งธ ์ธ์ž: dbService์˜ ์ปฌ๋ ‰์…˜ ์ค‘ "tweets" Docs ์ปฌ๋ ‰์…˜์„ ๊ฐ€์ ธ์™€ ๋‹ฌ๋ผ๊ณ  ์š”์ฒญ
  • ๋‘ ๋ฒˆ์งธ ์ธ์ž: ๊ทธ ์ค‘์—์„œ, userObj์˜ uid์™€ ๋™์ผํ•œ creatorID๋ฅผ ๊ฐ€์ง„ Docs๋งŒ ๊ฐ€์ ธ์™€ ๋‹ฌ๋ผ.

2.3. getDocs() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌํ•ด์˜จ ๋ชจ๋“  ๋ฌธ์„œ์˜ ๊ฒฐ๊ณผ ๊ฐ€์ ธ์˜ค๊ธฐ

where()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜๋Š” ๋ชจ๋“  ๋ฌธ์„œ๋ฅผ ์ฟผ๋ฆฌํ•œ ํ›„, getDocs() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ์‹œ

const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});

๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

import { authService, dbService } from "fbase";
import { collection, getDocs, query, where } from "firebase/firestore";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

//๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด prop์œผ๋กœ ๋ฐ›๊ธฐ
const Profile = ({ userObj }) => {
  const navigate = useNavigate();
  const onLogOutClick = () => {
    authService.signOut();
    //home์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ์œ„ํ•ด react router dom์˜ useNavigate() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    navigate("/");
  };

  //๋‚ด Tweets ์–ป๋Š” function ์ƒ์„ฑ
  const getMyTweets = async () => {
    
    //ํŠธ์œ— ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    //dbService์˜ ์ปฌ๋ ‰์…˜ ์ค‘ "tweets" Docs์—์„œ userObj์˜ uid์™€ ๋™์ผํ•œ creatorID๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ๋ฌธ์„œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ(์š”์ฒญ) ์ƒ์„ฑ
    const q = query(
      collection(dbService, "tweets"),
      where("creatorId", "==", userObj.uid)
    );
    
    //๐Ÿ”ฅgetDocs()๋ฉ”์„œ๋“œ๋กœ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    const querySnapshot = await getDocs(q);
    querySnapshot.forEach((doc) => {
      console.log(doc.id, "=>", doc.data());
    });
  };
  
  //๋‚ด Tweets ์–ป๋Š” function ํ˜ธ์ถœ
  useEffect(() => {
    getMyTweets();
  }, []);

  return (
    <>
      <button onClick={onLogOutClick}>๋กœ๊ทธ์•„์›ƒ</button>
    </>
  );
};

export default Profile;
  • creatorId๊ฐ€ ๊ฐ™์€ Tweets ๋ฌธ์„œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์„ฑ๊ณต!

์ด๋ ‡๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

2.4. ํŠธ์œ—ํ•œ ์‹œ๊ฐ„ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌํ•˜์—ฌ ๋ฌธ์„œ๊ฐ€์ ธ์˜ค๊ธฐ

- orderBy()
์˜ˆ์‹œ

import { query, orderBy } from "firebase/firestore";
//์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
//์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•œ ํ›„ ๊ฐ ์ฃผ ์•ˆ์—์„œ ์ธ๊ตฌ์— ๋”ฐ๋ผ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
const q = query(citiesRef, orderBy("state"), orderBy("population", "desc"));
import { authService, dbService } from "fbase";
//๐Ÿ”ฅ orderBy ๊ฐ€์ ธ์˜ค๊ธฐ
import { collection, getDocs, query, where, orderBy } from "firebase/firestore";
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

//๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด prop์œผ๋กœ ๋ฐ›๊ธฐ
const Profile = ({ userObj }) => {
  const navigate = useNavigate();
  const onLogOutClick = () => {
    authService.signOut();
    //home์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ ์œ„ํ•ด react router dom์˜ useNavigate() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    navigate("/");
  };

  //๋‚ด Tweets ์–ป๋Š” function ์ƒ์„ฑ
  const getMyTweets = async () => {
    
    //ํŠธ์œ— ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    //dbService์˜ ์ปฌ๋ ‰์…˜ ์ค‘ "tweets" Docs์—์„œ userObj์˜ uid์™€ ๋™์ผํ•œ creatorID๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ๋ฌธ์„œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ(์š”์ฒญ) ์ƒ์„ฑ
    //๐Ÿ”ฅ ํŠธ์œ—ํ•œ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ ฌํ•˜๊ธฐ
    const q = query(
      collection(dbService, "tweets"),
      where("creatorId", "==", userObj.uid),
      orderBy("createdAt", "desc")
    );
    
    //getDocs()๋ฉ”์„œ๋“œ๋กœ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    const querySnapshot = await getDocs(q);
    querySnapshot.forEach((doc) => {
      console.log(doc.id, "=>", doc.data());
    });
  };
  
  //๋‚ด Tweets ์–ป๋Š” function ํ˜ธ์ถœ
  useEffect(() => {
    getMyTweets();
  }, []);

  return (
    <>
      <button onClick={onLogOutClick}>๋กœ๊ทธ์•„์›ƒ</button>
    </>
  );
};

export default Profile;

2.5 index ์—๋Ÿฌ๋ฐœ์ƒ!

โš ๏ธ ์—๋Ÿฌ ๋ฐœ์ƒ
Uncaught (in promise) FirebaseError: The query requires an index. You can create it here: url ๋ธ”๋ผ๋ธ”๋ผ

Firestore๋Š” noSQL ๊ธฐ๋ฐ˜ DB๋ผ์„œ ๋ช‡๋ช‡ ๊ธฐ๋Šฅ์€ ์ด๋ ‡๊ฒŒ ์ž‘๋™๋  ์ˆ˜๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค๊ณ  ํ•œ๋‹ค.
ํ•ด๋‹น ์ฟผ๋ฆฌ์— index๊ฐ€ ํ•„์š”ํ•˜๋‹ค๊ณ  ํ•˜๋Š”๋ฐ ์ด๋Š” pre-made query๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค ์ค€๋น„๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก, ์ด ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฑฐ๋ผ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๊ฒŒ ์•Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค.

  • ์—๋Ÿฌ ๋œฌ url ํด๋ฆญํ•ด์„œ composite index(๋ณตํ•ฉ์ƒ‰์ธ)๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž ^3^..
  • ๋ณตํ•ฉ์ƒ‰์ธ ๋งŒ๋“ค๊ธฐ ์™„์„ฑ

  • ์˜ค์ผ€์ด! ์ฝ˜์†”์„ ํ™•์ธํ•ด ๋ณด์ž. createdAt ๋‚ด๋ฆผ์ฐจ์ˆœ(๊ฐ€์žฅ ๋จผ์ € ์“ด ํŠธ์œ—์ด ์ œ์ผ ์•„๋ž˜๋กœ)์œผ๋กœ ์ž˜ ๋‚˜์˜ค๊ณ  ์žˆ๋‹ค.


์š”์•ฝ

์ฟผ๋ฆฌ(์š”์ฒญ) ํ•„ํ„ฐ๋งํ•˜๊ธฐ

//โœ… v.9

import { authService, dbService } from "fbase";
import { collection, getDocs, query, where, orderBy } from "firebase/firestore";
import { useEffect } from "react";
//...

//1. ๋กœ๊ทธ์ธํ•œ ์œ ์ € ์ •๋ณด prop์œผ๋กœ ๋ฐ›๊ธฐ
const Profile = ({ userObj }) => {
//...

//2. ๋‚ด nweets ์–ป๋Š” function ์ƒ์„ฑ
const getMyNweets = async () => {
//3. ํŠธ์œ— ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
//3-1. dbService์˜ ์ปฌ๋ ‰์…˜ ์ค‘ "nweets" Docs์—์„œ userObj์˜ uid์™€ ๋™์ผํ•œ creatorID๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ๋ฌธ์„œ๋ฅผ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ(์š”์ฒญ) ์ƒ์„ฑ
const q = query(
collection(dbService, "nweets"),
where("creatorId", "==", userObj.uid),
orderBy("createdAt", "desc")
);

//3-2. getDocs()๋ฉ”์„œ๋“œ๋กœ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data());
});
};

//4. ๋‚ด nweets ์–ป๋Š” function ํ˜ธ์ถœ
useEffect(() => {
getMyTweets();
}, []);

return (
<>
๋กœ๊ทธ์•„์›ƒ
</>
);
};

export default Profile;
profile
Always have hope๐Ÿ€ & constant passion๐Ÿ”ฅ

0๊ฐœ์˜ ๋Œ“๊ธ€