스파르타코딩클럽 내일배움캠프 TIL47

한재창·2023년 1월 4일
0

노마드코더 리액트 네이티브

Todos

  • firebase를 사용하여 복습하기
  • 컴포넌트 분리해보기
  • 하나하나 주석을 달며 공부하기
// App.jsx

import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import { StyleSheet, View, SafeAreaView, TextInput, Alert } from "react-native";
import { theme } from "./colors";
import { dbService } from "./firebaseConfig";
import {
  collection,
  addDoc,
  query,
  orderBy,
  getDoc,
  doc,
  updateDoc,
  deleteDoc,
  onSnapshot,
} from "firebase/firestore";
import Header from "./components/Header";
import Items from "./components/Items";

export default function App() {
  // 카테고리가 어디 있는지 useState로 관리한다.
  const [category, setCategory] = useState(true);

  // input의 value를 관리하는 useState
  const [textValue, setTextValue] = useState("");

  // todo의 아이템을 저장하는 useState
  const [items, setItems] = useState([]);

  // 수정된 text를 저장하는 useState
  const [editText, setEditText] = useState();

  // 카테고리를 바꾸는 함수, 카테고리가 true면 Work, false면 Travel에 위치한다.
  const categoryChangeHandler = async (isCategory) => {
    setCategory(isCategory);

    // category 콜렉션에 있는 currentCategory의 doc을 isCategory로 업데이트해준다.
    await updateDoc(doc(dbService, "category", "currentCategory"), {
      category: isCategory,
    });
  };

  // 마운트 될 때 실행된다.
  useEffect(() => {
    // 1. onSnapshot API를 이용해서 items 콜렉션에 변경이 생길 때 마다
    // 2. items 콜렉션 안의 모든 document들을 불러와서 setItems 한다.
    const q = query(collection(dbService, "items"), orderBy("time", "desc"));

    // snapshot은 event처럼 기본적으로 제공해주는 매개변수
    onSnapshot(q, (snapshot) => {
      const newItems = snapshot.docs.map((doc) => {
        const newItem = {
          id: doc.id,
          ...doc.data(),
        };
        return newItem;
      });
      setItems(newItems);
    });

    // 마지막 카테고리가 저장된 값을 불러온다.
    // 리로드해도 마지막에 있던 카테고리에서 시작된다.
    const getCategory = async () => {
      const snapshot = await getDoc(
        doc(dbService, "category", "currentCategory")
      );
      setCategory(snapshot.data().category);
    };
    getCategory();
  }, []);

  // todos, travel를 데이터 베이스에 추가해주는 함수
  const addItemHandler = async () => {
    if (textValue === "") return alert("빈 값은 입력할 수 없습니다.");

    const newItems = {
      time: new Date(),
      text: textValue,
      category,
      isDone: false,
      isEdit: false,
    };
    await addDoc(collection(dbService, "items"), newItems);
    setTextValue("");
  };

  // item을 삭제하는 함수
  // item.id 값을 인자로 받아와서 deleteDoc 메서드를 사용해줌
  const removeItemHandler = async (id) => {
    Alert.alert("Remove Item", "Are you sure?", [
      {
        text: "Cancel",
        style: "cancel",
      },
      {
        text: "Delete",
        style: "destructive",
        onPress: async () => {
          await deleteDoc(doc(dbService, "items", id));
        },
      },
    ]);
  };

  // item 완료하는 함수
  // items에 있는 데이터 중 매개변수로 받아온 item.id와 같은 것을 찾고 그 값의 isDone를 반대로 업데이트 해준다.
  const doneCheckHandler = async (id) => {
    const item = items.find((item) => item.id === id);
    await updateDoc(doc(dbService, "items", id), {
      isDone: !item.isDone,
    });
  };

  // item 수정버튼 클릭시 input창이 뜨게하는 함수
  const editCheckHandler = async (id) => {
    const item = items.find((item) => item.id === id);
    console.log(item);
    await updateDoc(doc(dbService, "items", id), {
      isEdit: !item.isEdit,
    });
    setEditText(item.text);
  };

  // item 수정 함수
  const editItemHandler = async (id) => {
    await updateDoc(doc(dbService, "items", id), {
      text: editText,
      isEdit: false,
    });
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.wrap}>
        <Header
          category={category}
          categoryChangeHandler={categoryChangeHandler}
        />
        <TextInput
          returnKeyType="done"
          onSubmitEditing={addItemHandler}
          value={textValue}
          onChangeText={setTextValue}
          placeholder={category ? "Add a To do" : "Where do you want to go?"}
          style={styles.input}
        />
        <Items
          items={items}
          category={category}
          setEditText={setEditText}
          removeItemHandler={removeItemHandler}
          doneCheckHandler={doneCheckHandler}
          editCheckHandler={editCheckHandler}
          editItemHandler={editItemHandler}
        />
        <StatusBar style="light" />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: theme.background,
  },
  wrap: {
    paddingHorizontal: 20,
  },
  input: {
    backgroundColor: "white",
    paddingVertical: 15,
    paddingHorizontal: 20,
    borderRadius: 30,
    marginVertical: 20,
    fontSize: 15,
  },
});
// Header.jsx

import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { theme } from "../colors";

export default function Header({ category, categoryChangeHandler }) {
  return (
    <View style={styles.header}>
      <TouchableOpacity onPress={() => categoryChangeHandler(true)}>
        <Text
          style={{
            ...styles.btnText,
            color: category ? "white" : theme.grey,
          }}
        >
          Work
        </Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => categoryChangeHandler(false)}>
        <Text
          style={{
            ...styles.btnText,
            color: !category ? "white" : theme.grey,
          }}
        >
          Travel
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  header: {
    flexDirection: "row",
    justifyContent: "space-between",
  },
  btnText: {
    fontSize: 25,
    fontWeight: "bold",
  },
});
// Items.jsx

import {
  ScrollView,
  TextInput,
  View,
  Text,
  TouchableOpacity,
  StyleSheet,
} from "react-native";
import { Fontisto, AntDesign } from "@expo/vector-icons";
import { theme } from "../colors";

export default function Items({
  items,
  category,
  setEditText,
  removeItemHandler,
  doneCheckHandler,
  editCheckHandler,
  editItemHandler,
}) {
  return (
    <ScrollView>
      {items.map((item) => {
        if (category === item.category) {
          return (
            <View style={styles.item} key={item.id}>
              {item.isEdit ? (
                <TextInput
                  onSubmitEditing={() => editItemHandler(item.id)}
                  style={styles.editInput}
                  defaultValue={item.text}
                  onChangeText={setEditText}
                />
              ) : (
                <Text
                  style={{
                    ...styles.itemText,
                    textDecorationLine: item.isDone ? "line-through" : "none",
                  }}
                >
                  {item.text}
                </Text>
              )}

              <View style={styles.itemBtnBox}>
                <TouchableOpacity
                  onPress={() => editCheckHandler(item.id)}
                  style={styles.itemBtn}
                >
                  <AntDesign name="edit" size={20} color={theme.grey} />
                </TouchableOpacity>
                <TouchableOpacity
                  onPress={() => doneCheckHandler(item.id)}
                  style={{ ...styles.itemBtn, marginTop: 2 }}
                >
                  <Fontisto
                    name="checkbox-active"
                    size={15}
                    color={theme.grey}
                  />
                </TouchableOpacity>
                <TouchableOpacity onPress={() => removeItemHandler(item.id)}>
                  <Fontisto name="trash" size={18} color={theme.grey} />
                </TouchableOpacity>
              </View>
            </View>
          );
        }
      })}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  item: {
    backgroundColor: theme.itemBackground,
    marginBottom: 10,
    paddingVertical: 20,
    paddingHorizontal: 20,
    borderRadius: 15,
    flexDirection: "row",
    justifyContent: "space-between",
  },
  itemText: {
    color: "white",
    fontSize: 16,
    fontWeight: "500",
  },
  itemBtnBox: {
    flexDirection: "row",
  },
  itemBtn: {
    marginRight: 12,
  },
  editInput: {
    width: "70%",
    backgroundColor: theme.grey,
    color: "white",
  },
});
profile
취준 개발자

0개의 댓글