๐ŸŒŸ Twitter Clone: Getting the Tweets(cRud)

CRUD with firebase: โœ… Read(์ฝ๊ธฐ)-Getting the Tweets


ํŠธ์œ—์„ ์ž‘์„ฑํ•˜์—ฌ db์— create ํ•˜๋Š” ๊ฒƒ์„ ์™„๋ฃŒํ–ˆ์œผ๋‹ˆ ์ด์ œ ํŒŒ์ด์–ด์Šคํ† ์–ดdb์—์„œ ์ž‘์„ฑํ•œ ํŠธ์œ—์„ ๊ฐ€์ ธ์™€ read! ์ฝ์–ด๋ณด์ž!


1. ์„ธํŒ…


  1. firebase/firestore๋กœ ๋ถ€ํ„ฐ query์™€ getDocs๋ฅผ import ํ•œ๋‹ค.
import {
  collection,
  addDoc,
  serverTimestamp,
  //๊ฐ€์ ธ์˜ค๊ธฐ
  query,
  getDocs,
} from "firebase/firestore";
  1. Home ์ปดํฌ๋„ŒํŠธ์— useState()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์„ฑํ•œ ํŠธ์œ—์„ ๊ฐ€์ ธ์˜ค๋Š” ์Šคํ…Œ์ดํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.
    ์ด๋•Œ ๊ธฐ๋ณธ ๊ฐ’์€ ๋นˆ ๋ฐฐ์—ด(array)๋กœ ์„ค์ •ํ•˜์—ฌ ์ž‘์„ฑํ•œ ํŠธ์œ— ๋ฐ์ดํ„ฐ๋“ค์ด ๋ฐฐ์—ด์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์ž.
//๐Ÿ”ฅ์ž‘์„ฑํ•œ ํŠธ์œ— ๊ฐ€์ ธ์˜ค๊ธฐ: ๊ธฐ๋ณธ ๊ฐ’์€ ๋นˆ ๋ฐฐ์—ด
const [tweets, setTweets] = useState([]);
  1. Home ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๋ฉด useEffect(fn, []);๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ์œ— ๋‚ด์šฉ๋“ค์„ ๊ฐ€์ ธ์˜ค์ž.
  • ๊ทธ๋Ÿฌ๊ธฐ ์œ„ํ•ด ํŠธ์œ— ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜ getTweets์„ ๋งŒ๋“ค๊ณ  ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ž.
    • ์ด๋Š” async๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ณ„ ํ•จ์ˆ˜๋กœ ๋ฐ–์œผ๋กœ ๋นผ์„œ ๋งŒ๋“  ํ›„, useEffect()์—์„œ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด์„œ์ด๋‹ค.
useEffect(() => {
  getTweets();
}, []);

์šฉ์–ด ์•Œ๊ณ  ๋„˜์–ด๊ฐ€๊ธฐ

  • ๋งˆ์šดํŠธ(mount)
    : ํŒŒ์ผ์‹œ์Šคํ…œ ๊ตฌ์กฐ ๋‚ด์— ์žˆ๋Š” ์ผ๋ จ์˜ ํŒŒ์ผ๋“ค์„ ์‚ฌ์šฉ์ž๋‚˜ ์‚ฌ์šฉ์ž ๊ทธ๋ฃน๋“ค์ด ์ด์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ

2. ํŠธ์œ— ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ฌ getTweets ํ•จ์ˆ˜์˜ ๋‚ด์šฉ์„ ์ฑ„์›Œ๋ณด์ž.


์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•  firestore์˜ ๋ฉ”์„œ๋“œ

  • getDocs()๋Š” ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ QuerySnapshot์œผ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • getDocs๋Š” promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— async, await์„ ์‚ฌ์šฉํ•˜์ž.

  • QuerySnapshot ํด๋ž˜์Šค
    • QuerySnapshot์—๋Š” ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” 0๊ฐœ ์ด์ƒ์˜ DocumentSnapshot ๊ฐœ์ฒด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.
    • ๋‹คํ๋จผํŠธ๋Š”, docs ์†์„ฑ์„ ํ†ตํ•ด ๋ฐฐ์—ด๋กœ ์•ก์„ธ์Šคํ•˜๊ฑฐ๋‚˜ / forEach ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ด๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฌธ์„œ์˜ ์ˆ˜๋Š” empty ๋ฐ size ์†์„ฑ์„ ํ†ตํ•ด ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
const getTweets = async () => {
  /*1. DB์—์„œ ์ปฌ๋ ‰์…˜ ๊ฐ€์ ธ์˜ค๊ธฐ
  dbService์— ์žˆ๋Š” tweets ์ปฌ๋ ‰์…˜์„ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ*/
  const q = query(collection(dbService, "tweets"));
  
  /*2. ์ปฌ๋ ‰์…˜์˜ ๋‹คํ๋จผํŠธ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ธฐ
    - ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ QuerySnapshot์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉด tweets ์ปฌ๋ ‰์…˜์˜ document๋“ค์„ ์–ป๋Š”๋‹ค.
    - ๋”ฐ๋ผ์„œ querySnapshot์€ tweets ์ปฌ๋ ‰์…˜์˜ document๋“ค์„ ๋ชจ์•„๋†“์€ ๋ฐฐ์—ด์ด๋‹ค.*/
  const querySnapshot = await getDocs(q);

  //3. ๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ด
  querySnapshot.forEach((document) => {
    /*๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ดํ•  ๋•Œ {data, id} ์˜ค๋ธŒ์ ํŠธ ํ˜•ํƒœ๋กœ ๋‚˜์—ดํ•˜์ž.
    - document์˜ data๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด data() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    - ํŠธ์œ— ํ•˜๋‚˜์”ฉ ๋‚˜์—ด ํ•  ๋•Œ ์“ธ key ๊ฐ’ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— id ํ• ๋‹น*/
    const tweetObj = {
      ...document.data(),
      id: document.id,
    };
    //console.log(tweetObj);
    //- setTweets() ๋ชจ๋””ํŒŒ์ด์–ด๋กœ ์ด์ „ tweets(prev)์— ๋Œ€ํ•ด, ์ƒˆ๋กœ์šด ๋ฐฐ์—ด(์ƒˆ๋กœ ์ž‘์„ฑํ•œ ํŠธ์œ—๊ณผ, ...๊ทธ ์ด์ „ ๊ฒƒ๋“ค)์„ ๋ฆฌํ„ดํ•ด์ฃผ์ž.
    setTweets((prev) => [tweetObj, ...prev]);
  });
};
  1. ๋จผ์ € ํŒŒ์ด์–ด์Šคํ† ์–ด์—์„œ ์‚ฌ์šฉํ•  ์ปฌ๋ ‰์…˜์„ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
    query()๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, dbService์— ์žˆ๋Š” tweets ์ปฌ๋ ‰์…˜์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค์ž.

  2. ์ปฌ๋ ‰์…˜์„ ๊ฐ€์ ธ์™”์œผ๋‹ˆ tweets ์ปฌ๋ ‰์…˜์˜ document๋“ค์„ ๊ฐ€์ ธ์˜ค์ž.

    • ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ QuerySnapshot์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” getDocs()๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    • ์ด ๋ฉ”์„œ๋“œ์— ๋ฐฉ๊ธˆ ๋งŒ๋“  ์ฟผ๋ฆฌ(q)๋ฅผ ๋ณด๋‚ด๊ณ , ์ด ๊ฐ’์„ querySnapshot๋ณ€์ˆ˜๋กœ ํ• ๋‹นํ•˜์ž.
    • getDocs()์„ ํ˜ธ์ถœํ•  ๋•Œ๋Š” await์„ ์‚ฌ์šฉํ•˜์—ฌ ๋™๊ธฐ์ฒ˜๋ฆฌ ํ•ด์ฃผ์ž.
  3. ๋ชจ๋“  ๋‹คํ๋จผํŠธ๋ฅผ ๊ฐ€์ง„ ๋ฐฐ์—ด์ธ querySnapshot์—์„œ ๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” forEach๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
    ๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ ํ˜•ํƒœ๋กœ ๋‚˜์—ด๋˜๊ฒŒ ํ•˜์ž.

  • {๋‹คํ๋จผํŠธ์˜ data, id}
    • ๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ์˜ data๋Š” data() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.
    • ๋‹คํ๋จผํŠธ์— id๋ฅผ ํ• ๋‹นํ•˜์ž.
      • ๊ทธ๋ƒฅ data๋งŒ ๊ฐ€์ ธ์˜ฌ๊ฑฐ๋ฉด ์˜ค๋ธŒ์ ํŠธ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋””๋น„์—์„œ ๋ฐ›์•„์˜จ ๋‹คํ๋จผํŠธ์ธ ํŠธ์œ— ๋‚ด์šฉ์„ ํƒœ๊ทธ๋กœ ์ž‘์„ฑํ•ด์„œ ๋‚˜์—ดํ•  ๋•Œ, ํŠธ์œ— ๋‚ด์šฉ ๊ฐ๊ฐ์— key๊ฐ’์„ ํ• ๋‹นํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— id๋ฅผ ํ• ๋‹นํ•ด ์คฌ๋‹ค. ๋”ฐ๋ผ์„œ ์˜ค๋ธŒ์ ํŠธ ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹จ ์†Œ๋ฆฌ.
      • ๊ฒฐ๋ก ์ ์œผ๋กœ forEach ์จ์„œ ๋ชจ๋“  ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ดํ•˜๋ฉด ์ด๋Ÿฐ ๋ชจ์–‘์ƒˆ๋‹ค. console.log(tweetObj);๋ฅผ ํ•ด๋ณด๋ฉด ์ปฌ๋ ‰์…˜์— ๋“ค์–ด ์žˆ๋Š” ๋‹คํ๋จผํŠธ๋“ค์ด ๊ฐ๊ฐ์˜ ์˜ค๋ธŒ์ ํŠธ๋กœ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (ํŠธ์œ— ๋‚ด์šฉ์€ ์“ฐ๋ฃจํ•ฉ์‹œ๋‹น..^^~)
    • ๊ทธ๋Ÿฌ๊ณ  ๋‚˜์„œ setTweets() ๋ชจ๋””ํŒŒ์ด์–ด๋กœ state๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ์ž. ๊ทธ๋ž˜์•ผ ๋ฐฉ๊ธˆ ์“ด ํŠธ์œ—๋„ tweets state์— ๋“ค์–ด๊ฐ„๋‹ค.
      • ์ด ๋•Œ, ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ˜„์žฌ ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•˜์ž.
      • ES6์˜ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž...๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ์ด์ „ tweets(prev)์— ๋Œ€ํ•ด, ์ƒˆ๋กœ์šด ๋ฐฐ์—ด(์ƒˆ๋กœ ์ž‘์„ฑํ•œ ํŠธ์œ—๊ณผ, ...๊ทธ ์ด์ „ ๊ฒƒ๋“ค)์„ ๋ฆฌํ„ดํ•ด์ฃผ์ž.

3. ํŠธ์œ— ๋‚ด์šฉ ๋‚˜์—ดํ•˜๊ธฐ


  1. ํŠธ์œ— ๋‚ด์šฉ์ด ๋“  tweets state๋Š” ๋ฐฐ์—ด์ด๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐฐ์—ด์˜ item์„ ๋‚˜์—ดํ•˜๊ธฐ ์œ„ํ•ด map() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ž.
    <div>
     {tweets.map((tweet) => (
       //tweetObj ๋งŒ๋“ค ๋•Œ ๊ฐ๊ฐ์˜ tweet์— ํ• ๋‹นํ•œ id ๊ฐ’์„ div์˜ key์— ๋„ฃ์–ด์ฃผ์ž
       <div key={tweet.id}>
         <h4>{tweet.tweet}</h4>
       </div>
     ))}
    </div>

์ „์ฒด ์ฝ”๋“œ

๐Ÿ“ Home.js


import React, { useEffect, useState } from "react";
import { dbService } from "fbase";
import {
  collection,
  addDoc,
  serverTimestamp,
  query,
  getDocs,
} from "firebase/firestore";

const Home = () => {
  //ํ™ˆ์—์„œ ํŠธ์œ— ๋‚ด์šฉ ์ž‘์„ฑํ•˜๋Š” ํผ
  const [tweet, setTweet] = useState("");

  //0. ์ž‘์„ฑํ•œ ํŠธ์œ— ๊ฐ€์ ธ์˜ค๊ธฐ: ๊ธฐ๋ณธ ๊ฐ’์€ ๋นˆ ๋ฐฐ์—ด
  const [tweets, setTweets] = useState([]);

  const getTweets = async () => {
    /*1. DB์—์„œ ์ปฌ๋ ‰์…˜ ๊ฐ€์ ธ์˜ค๊ธฐ
    dbService์— ์žˆ๋Š” tweets ์ปฌ๋ ‰์…˜์„ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ*/
    const q = query(collection(dbService, "tweets"));

    /*2. ์ปฌ๋ ‰์…˜์˜ ๋‹คํ๋จผํŠธ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ธฐ
    - ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ QuerySnapshot์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉด tweets ์ปฌ๋ ‰์…˜์˜ document๋“ค์„ ์–ป๋Š”๋‹ค.
    - ๋”ฐ๋ผ์„œ querySnapshot์€ tweets ์ปฌ๋ ‰์…˜์˜ document๋“ค์„ ๋ชจ์•„๋†“์€ ๋ฐฐ์—ด์ด๋‹ค.*/
    const querySnapshot = await getDocs(q);

    //3. ๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ด
    querySnapshot.forEach((document) => {
      /*๊ฐ๊ฐ์˜ ๋‹คํ๋จผํŠธ๋ฅผ ๋‚˜์—ดํ•  ๋•Œ {data, id} ์˜ค๋ธŒ์ ํŠธ ํ˜•ํƒœ๋กœ ๋‚˜์—ดํ•˜์ž.
    - document์˜ data๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด data() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    - ํŠธ์œ— ํ•˜๋‚˜์”ฉ ๋‚˜์—ด ํ•  ๋•Œ ์“ธ key ๊ฐ’ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— id ํ• ๋‹น*/
      const tweetObj = {
        ...document.data(),
        id: document.id,
      };
      //- setTweets() ๋ชจ๋””ํŒŒ์ด์–ด๋กœ ์ด์ „ tweets(prev)์— ๋Œ€ํ•ด, ์ƒˆ๋กœ์šด ๋ฐฐ์—ด(์ƒˆ๋กœ ์ž‘์„ฑํ•œ ํŠธ์œ—๊ณผ, ...๊ทธ ์ด์ „ ๊ฒƒ๋“ค)์„ ๋ฆฌํ„ดํ•ด์ฃผ์ž.
      setTweets((prev) => [tweetObj, ...prev]);
    });
  };

  //4. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๋ฉด useEffect()์‚ฌ์šฉ
  useEffect(() => {
    getTweets();
  }, []);

  const onSubmit = async (event) => {
    event.preventDefault();
    //ํŠธ์œ—ํ•˜๊ธฐ ๋ˆ„๋ฅด๋ฉด ์ƒˆ๋กœ์šด document ์ƒ์„ฑํ•˜๊ธฐ
    try {
      const docRef = await addDoc(collection(dbService, "tweets"), {
        tweet, // tweet(๋‹คํ๋จผํŠธ์˜ key): tweet(value๋กœ tweet state ๊ฐ’)
        createdAt: serverTimestamp(), //Date.now(),๋กœ ํ•ด๋„ ๋˜์ง€๋งŒ ์ด์™• ์žˆ๋Š”๊ฑฐ ํ•จ ์จ๋ณด์ž(ํƒ€์ž„์กด ๋™๋ถ์•„3 = ์„œ์šธ๋กœ ์„ค์ •๋˜์–ด ์žˆ์Œ)
      });
      console.log("Document written with ID: ", docRef.id);
    } catch (error) {
      console.error("Error adding document: ", error);
    }
    //state ๋น„์›Œ์„œ form ๋น„์šฐ๊ธฐ
    setTweet("");
  };

  const onChange = (event) => {
    const {
      target: { value },
    } = event;
    setTweet(value);
    //console.log(tweet);
  };

  return (
    <>
      <div>
        <form onSubmit={onSubmit}>
          <input
            type="text"
            placeholder="๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๊ณ  ์žˆ๋‚˜์š”?"
            maxLength={120}
            value={tweet}
            onChange={onChange}
          />
          <input type="submit" value="ํŠธ์œ—ํ•˜๊ธฐ" />
        </form>
      </div>
      {/*DB์—์„œ ๊ฐ€์ ธ์˜จ ํŠธ์œ„ํ„ฐ ๋‚˜์—ด*/}
      <div>
        {tweets.map((tweet) => (
          //tweetObj ๋งŒ๋“ค ๋•Œ ๊ฐ๊ฐ์˜ tweet์— ํ• ๋‹นํ•œ id ๊ฐ’์„ div์˜ key์— ๋„ฃ์–ด์ฃผ์ž
          <div key={tweet.id}>
            <h4>{tweet.tweet}</h4>
          </div>
        ))}
      </div>
    </>
  );
};

export default Home;

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ