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

한재창·2023년 1월 13일
0

프로젝트

오늘 한 일 1

  • 관심목록 기능 및 페이지 완성
    • onSnapshot 메서드를 이용하고 상위컴포넌트에서 useState로 데이터를 저장해주었는데, 메인 페이지를 벗어나는 순간 코드들이 죽어 업데이트 된 데이터가 넘어오지 않는 현상이 발생하였다.
      • getDocs 메서드를 이용해서 컴포넌트마다 데이터를 불러오고 useState에 불러온 데이터를 저장해주니 해결되었다.
    • 내 아이디가 관심목록에 추가한 것만 목록에 나타나야해서 데이터베이스 키-값에 userId: authService.currentUser.uid를 추가해주었다.
    • if문을 사용해서 내가 눌렀던 것이 아니라면 데이터베이스에 추가해주었다.
    • 하트 버튼을 누르면 바로 렌더링 되지 않는 현상이 발생하였다.
      • 원래는 useState로 if문을 이용해서 isLike의 true : false 확인했다.
      • 변수에 저장해서 if문을 이용해서 확인하니 잘 작동하였다.
// MainCard.jsx

import {
  View,
  Text,
  TouchableOpacity,
  Image,
  SafeAreaView,
  ScrollView,
  useColorScheme,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import styled from '@emotion/native';
import {
  onSnapshot,
  collection,
  addDoc,
  setDoc,
  query,
  orderBy,
  getDocs,
  getDoc,
  doc,
  updateDoc,
  deleteDoc,
} from 'firebase/firestore';
import { dbService, authService } from '../firebase';
import { useCallback, useEffect, useState } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { v4 as uuidv4 } from 'uuid';
import DropShadow from 'react-native-drop-shadow';
import { DARK_COLOR } from '../colors';

export default function MainCard({ item }) {
  const { navigate } = useNavigation();
  const [items, setItems] = useState([]);
  const isDark = useColorScheme() === 'dark';
  const q = query(collection(dbService, 'isLike'));

  // 데이터로 넘겨받은 item 중에서 get으로 받아온 item의 desertionNo가 같고,
  // get으로 받아온 item의 userId가 로그인한 아이디가 같은 것을 변수에 저장해주고
  // 변수의 조건과 맞지 않고 로그인 했을때에만 isLike에 데이터를 추가해준다.
  // 데이터를 추가하고 최신 데이터를 가져오기 위해서 getData 해준다.
  const addIsLike = async (data) => {
    const selectedItem = items.find(
      (item) =>
        item.desertionNo === data.desertionNo &&
        item.userId === authService?.currentUser?.uid
    );
    if (!selectedItem && !!authService.currentUser) {
      const id = uuidv4();
      await setDoc(doc(dbService, 'isLike', id), {
        ...item,
        id,
        isLike: false,
        userId: authService?.currentUser?.uid,
      });
      getData();
    }
  };

  const getData = async () => {
    const querySnapshot = await getDocs(q);
    const itemArray = [];
    querySnapshot.forEach((doc) => {
      itemArray.push(doc.data());
    });
    setItems(itemArray);
  };

  useEffect(() => {
    getData();
  }, []);

  return (
    <TouchableOpacity
      onPress={() => {
        addIsLike(item);
        navigate('Detail', {
          params: { data: item },
        });
      }}
    >
      <DropShadow
        style={{
          shadowColor: '#000',
          shadowOffset: {
            width: 0,
            height: 5,
          },
          shadowOpacity: 0.29,
          shadowRadius: 4.65,
        }}
      >
        <SingleCard style={{ backgroundColor: isDark ? '#212123' : 'white' }}>
          <AnimalCardPicture>
            <AnimalPic source={{ url: `${item.popfile}` }} />
          </AnimalCardPicture>
          <AnimalCardType>
            <TextC style={{ color: isDark ? DARK_COLOR : 'black' }}>성별</TextC>
            <TextC style={{ color: isDark ? DARK_COLOR : 'black' }}>품종</TextC>
            <TextC style={{ color: isDark ? DARK_COLOR : 'black' }}>나이</TextC>
            <TextC style={{ color: isDark ? DARK_COLOR : 'black' }}>지역</TextC>
            <TextC style={{ color: isDark ? DARK_COLOR : 'black' }}>
              등록일
            </TextC>
          </AnimalCardType>
          <AnimalCardDescription>
            <AnimalCardGender style={{ color: isDark ? 'white' : 'black' }}>
              {item.sexCd === 'M' ? '남' : 'W' ? '여' : '중성'}
            </AnimalCardGender>
            <AnimalCardKind style={{ color: isDark ? 'white' : 'black' }}>
              {item.kindCd}
            </AnimalCardKind>
            <AnimalCardAge style={{ color: isDark ? 'white' : 'black' }}>
              {item.age}
            </AnimalCardAge>
            <AnimalCardLocation style={{ color: isDark ? 'white' : 'black' }}>
              {item.orgNm.slice(0, 2)}
              {/* {item.orgNm.length > 2 && '...'} */}
            </AnimalCardLocation>
            <AnimalCardDate style={{ color: isDark ? 'white' : 'black' }}>
              {item.happenDt}
            </AnimalCardDate>
          </AnimalCardDescription>
        </SingleCard>
      </DropShadow>
    </TouchableOpacity>
  );
}

const AnimalPic = styled.Image`
  width: 120px;
  height: 120px;
  border-radius: 70%;
`;

const TextC = styled.Text`
  font-size: 15px;
  font-weight: bold;
  margin-top: 8px;
`;

const SingleCard = styled.View`
  margin-left: 2.5%;
  flex-direction: row;
  align-items: center;
  width: 95%;
  height: 170px;
  margin-top: 15px;
  border-radius: 10px;
  background-color: #fff;
`;

const AnimalCardPicture = styled.View`
  margin-left: 25px;
  width: 120px;
  height: 120px;
  border-radius: 70%;
  background-color: #b3b3b3;
`;

const AnimalCardType = styled.View`
  margin-left: 30px;
  font-size: 25px;
  font-weight: bold;
`;

const AnimalCardDescription = styled.View`
  margin-left: 20px;
  position: relative;
`;

const AnimalCardKind = styled.Text`
  margin-top: 9px;
  position: relative;
`;

const AnimalCardGender = styled.Text`
  margin-top: 9px;
  position: relative;
`;

const AnimalCardAge = styled.Text`
  margin-top: 8px;
  position: relative;
`;

const AnimalCardLocation = styled.Text`
  margin-top: 8px;
  position: relative;
`;

const AnimalCardDate = styled.Text`
  margin-top: 6px;
  position: relative;
`;
// Details.jsx

import styled from '@emotion/native';
import { useState, useEffect, useCallback } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  useColorScheme,
  Alert,
} from 'react-native';
import DropShadow from 'react-native-drop-shadow';
import { SCREEN_HEIGHT, SCREEN_WIDTH } from '../../utils';
import { AntDesign } from '@expo/vector-icons';
import Item from './Item';
import {
  onSnapshot,
  collection,
  addDoc,
  setDoc,
  query,
  orderBy,
  getDocs,
  getDoc,
  doc,
  updateDoc,
  deleteDoc,
} from 'firebase/firestore';
import { dbService, authService } from '../../firebase';
import { useFocusEffect } from '@react-navigation/native';

export default function Details({ data }) {
  const [items, setItems] = useState([]);
  const isDark = useColorScheme() === 'dark';

  // 상태에 따라서 변수도 바뀐다.
  const checkLike = items?.find(
    (item) =>
      item?.desertionNo === data?.desertionNo &&
      item.userId === authService?.currentUser?.uid
  )?.isLike;

  const q = query(collection(dbService, 'isLike'));

  const getData = async () => {
    const querySnapshot = await getDocs(q);
    const itemArray = [];
    querySnapshot.forEach((doc) => {
      itemArray.push(doc.data());
    });
    setItems(itemArray);
  };

  useFocusEffect(
    useCallback(() => {
      getData();

      return () => {
        getData();
      };
    }, [])
  );

  // 좋아요 버튼을 누르면 실행되는 함수
  // 버튼을 누르면 item의 desertionNo를 매개변수로 받은 뒤 그 값과 getData해 온 것들 중에 같고,
  // getData해 온 것들 중 userId와 로그인한 아이디가 같은 것을 변수로 지정해준다.
  // 변수에 저장된 것은 파이어 스토어의 isLike에 있는 것들 중 방금 내가 클릭한 것과 같은 데이터이다.
  // 그 데이터의 id값은 doc.id와 같기 때문에 choiceItem.id를 commentRef에 저장해서 updateDoc 할 때 사용해준다.
  // 최신 데이터를 받아오기 위해 getData를 실행해준다.
  const isLikeChangeHandler = async (desertionNo) => {
    const choiceItem = items.find(
      (item) =>
        item.desertionNo === desertionNo &&
        item.userId === authService?.currentUser?.uid
    );

    const commentRef = doc(dbService, 'isLike', choiceItem.id);

    await updateDoc(commentRef, {
      isLike: !choiceItem.isLike,
    });
    getData();
  };

  return (
    <>
      <ScrollWrap style={{ backgroundColor: isDark ? '#1B1D21' : 'white' }}>
        <DetailPictureBox>
          <DetailImage
            source={{
              url: `${data.popfile}`,
            }}
            style={StyleSheet.absoluteFill}
          />
          <HeartWrapper
            onPress={() => {
              isLikeChangeHandler(data.desertionNo);
              checkLike
                ? Alert.alert('관심목록에서 제거되었습니다.')
                : Alert.alert('관심목록에 추가되었습니다.');
            }}
          >
            {!authService.currentUser ? null : checkLike ? (
              <AntDesign name='heart' size={24} color='red' />
            ) : (
              <AntDesign name='hearto' size={24} color='red' />
            )}
          </HeartWrapper>
        </DetailPictureBox>

        <DropShadow
          style={{
            shadowColor: '#000',
            shadowOffset: {
              width: 0,
              height: 5,
            },
            shadowOpacity: 0.29,
            shadowRadius: 4.65,
          }}
        >
          <Item data={data} />
        </DropShadow>
      </ScrollWrap>
    </>
  );
}

const ScrollWrap = styled.View`
  padding: 5%;
`;

const DetailImage = styled.Image`
  height: ${SCREEN_HEIGHT / 3 + 'px'};
  /* width: ${SCREEN_WIDTH}; */
  width: 100%;
  border-radius: 10%;
  position: relative;
`;

const DetailPictureBox = styled.View`
  width: ${SCREEN_WIDTH};
  height: ${SCREEN_HEIGHT / 3 + 'px'};
  border-radius: 10%;
  margin-bottom: 5%;
  background-color: #b3b3b3;
`;

const HeartWrapper = styled.TouchableOpacity`
  position: absolute;
  margin-top: 10px;
  margin-left: 10px;
`;

오늘 한 일 2

  • useNavigation 뒤로가기 수정
    • 원래 navigation 폴더에서 뒤로가기를 한 번에 해주었는데 뒤로가기를 누르면 필터페이지로 가는 현상이 발생하였다.
    • 뒤로가기가 필요한 각각의 컴포넌트에서 useFocusEffect를 적용해서 navigation의 setOptions를 이용해주었더니 잘 작동하였다.
// 뒤로가기 버튼이 필요한 컴포넌트에 적용한 코드

  useFocusEffect(
    useCallback(() => {
      setOptions({
        headerLeft: () => (
          <TouchableOpacity onPress={() => goBack()}>
            <AntDesign
              name='left'
              size={24}
            />
          </TouchableOpacity>
        ),
      });
    }, [])
  );

오늘 한 일 3

  • Github로 팀원들과 코드 합치기
    • 지금까지 한 작업들을 모두 합쳐서 잘 작동하는지 해보았다.
    • 깃허브에 코드가 잘못 pull & push 되어서 수정하는데 시간이 걸렸다.
    • 잘못된 코드를 찾기 너무 어려워서 새벽까지 계속하였고 결국에는 찾아서 수정하니 잘 되었다.

프로젝트 후 느낀점

  • 다른 어려운 것도 많았지만 이번 프로젝트는 git이 말썽을 많이 부려 마음 고생을 심하게 했다.
  • git 공부를 다시 해야겠다.
profile
취준 개발자

0개의 댓글