프로젝트/할일목록 앱 (RN)

24. 드롭다운 선택시 선택한 아이템 상단에 표시하기

syleemomo 2023. 11. 6. 18:14
728x90
import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/MaterialIcons'

import HomeScreen from './screens/HomeScreen';
import CalendarScreen from './screens/CalendarScreen';
import DashBoardSceen from './screens/DashBoardScreen';
import SettingsScreen from './screens/SettingsScreen';

import DropdownCategory from './components/DropdownCategory'

import { // 할일목록 조회를 위한 유틸리티 함수 추가
  getCollection,
} from './apis/firebase'

const CATEGORY_DROPDOWN_TITLE = '카테고리'
const YEAR_DROPDOWN_TITLE = '년'
const MONTH_DROPDOWN_TITLE = '월'

// const Stack = createNativeStackNavigator()
const Tab = createBottomTabNavigator()

export default function App({navigation, route}) {
  const [todos, setTodos] = useState([]) // 할일목록 상태 (HomeScreen -> App 이동)
  const [loading, setLoading] = useState(true) // 할일목록 상태 (HomeScreen -> App 이동)
  
  const [caretType, setCaretType] = useState(false)
  const [yearCaret, setYearCaret] = useState(false)
  const [monthCaret, setMonthCaret] = useState(false)
  const [numOfTodosToday, setNumOfTodosToday] = useState(0)
  const [categoryTitles, setCategoryTitles] = useState({ CATEGORY_DROPDOWN_TITLE: null, YEAR_DROPDOWN_TITLE: null, MONTH_DROPDOWN_TITLE: null })

  const { userInfo } = route.params
  console.log("로그인한 사용자 정보: ", userInfo)

  useEffect(() => { // 할일목록 조회 (HomeScreen -> App 이동)
    function onResult(querySnapshot){
      const list = []
      querySnapshot.forEach(doc => {
        console.log(doc.data())
        list.push({
          ...doc.data(),
          id: doc.id,
        })
      })
      setTodos(list)
      setLoading(false)
    }
    function onError(error){
      console.error(`${error} occured when reading todos`)
    }
    // 문제점 : onSnapshot 을 사용시 다른 사용자가 할일을 추가하면 전체할일목록이 업데이트되기 때문에 나의 화면도 새로고침됨 
    return getCollection('todos', 
                          onResult, onError,
                          { exists: true, condition: [['userEmail', '==', userInfo.email]] },
                          null, null)
  }, [])

  if (loading) {
    return (
      <View style={styles.block}>
        <ActivityIndicator size="large" color="#0047AB"/>
        <Text style={styles.loadingText}>Loading ...</Text>
      </View> 
    )
  }

  return (
    <>
      <Tab.Navigator initialRouteName = "Home" screenOptions={{
        tabBarActiveTintColor: '#a8c8ffff',
        // tabBarStyle: {
        //   backgroundColor: '#333'
        // }
      }}>
        <Tab.Screen name="Home" children={(props) => <HomeScreen 
                                                        {...props} 
                                                        caretType={caretType} 
                                                        setCaretType={setCaretType} 
                                                        todos={todos} loading={loading} 
                                                        setNumOfTodosToday={setNumOfTodosToday} 
                                                        userInfo={userInfo} 
                                                        setCategoryTitles={setCategoryTitles} 
                                                        categoryTitles={categoryTitles}
                                                        CATEGORY_DROPDOWN_TITLE={CATEGORY_DROPDOWN_TITLE}
                                                        />} options={{
          title: '홈',
          tabBarIcon: ({ color, size }) => <Icon name="home" color={color} size={size}/>,
          headerTitle: (props) => <DropdownCategory {...props} caretType={caretType} setCaretType={setCaretType} categoryTitle={categoryTitles[CATEGORY_DROPDOWN_TITLE] ? categoryTitles[CATEGORY_DROPDOWN_TITLE] : CATEGORY_DROPDOWN_TITLE}/>,
          headerStyle: {
            backgroundColor: '#a8c8ffff',
          },
          headerTitleStyle: {
            fontWeight: 'bold',
            color: '#fff'
          },
          tabBarBadge: numOfTodosToday
        }}/>
        <Tab.Screen name="Calendar" children={(props) => <CalendarScreen 
                                                          {...props}
                                                          yearCaret={yearCaret}
                                                          setYearCaret={setYearCaret}
                                                          monthCaret={monthCaret}
                                                          setMonthCaret={setMonthCaret}
                                                          setCategoryTitles={setCategoryTitles}
                                                          categoryTitles={categoryTitles}
                                                          YEAR_DROPDOWN_TITLE={YEAR_DROPDOWN_TITLE}
                                                          MONTH_DROPDOWN_TITLE={MONTH_DROPDOWN_TITLE}
                                                          />} options={{
          title: '달력',
          tabBarIcon: ({ color, size }) => <Icon name="calendar-month" color={color} size={size}/>,
          headerTitle: (props) => (<View style={{flexDirection: 'row'}}>
            <DropdownCategory {...props} caretType={yearCaret} setCaretType={setYearCaret} categoryTitle={categoryTitles[YEAR_DROPDOWN_TITLE] ? categoryTitles[YEAR_DROPDOWN_TITLE] : YEAR_DROPDOWN_TITLE}/>
            <DropdownCategory {...props} caretType={monthCaret} setCaretType={setMonthCaret} categoryTitle={categoryTitles[MONTH_DROPDOWN_TITLE] ? categoryTitles[MONTH_DROPDOWN_TITLE] : MONTH_DROPDOWN_TITLE}/>
          </View>),
          headerStyle: {
            backgroundColor: '#a8c8ffff',
          },
          headerTitleStyle: {
            fontWeight: 'bold',
            color: '#fff'
          },
        }}/>
        <Tab.Screen name="DashBoard" children={(props) => <DashBoardSceen todos={todos}/>} options={{
          title: '통계',
          tabBarIcon: ({ color, size }) => <Icon name="dashboard" color={color} size={size}/>
        }}/>
        <Tab.Screen name="Settings" component={SettingsScreen} options={{
          title: '설정',
          tabBarIcon: ({ color, size }) => <Icon name="settings" color={color} size={size}/>
        }}/>
      </Tab.Navigator>
    </>
  );
}

const styles = StyleSheet.create({
  block: {
    flex: 1,
    backgroundColor: '#a8c8ffff',
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingText: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#fff',
    marginTop: 10,
    textAlign: 'center'
  }
})

App 컴포넌트를 위와 같이 수정한다.

const CATEGORY_DROPDOWN_TITLE = '카테고리'
const YEAR_DROPDOWN_TITLE = '년'
const MONTH_DROPDOWN_TITLE = '월'

각 드롭다운 버튼에 대한 이름을 상수로 설정하여 재활용하도록 한다. 이는 유지보수의 편의를 위함도 포함된다. 

const [categoryTitles, setCategoryTitles] = useState({ CATEGORY_DROPDOWN_TITLE: null, YEAR_DROPDOWN_TITLE: null, MONTH_DROPDOWN_TITLE: null })

모든 드롭다운 타이틀을 관리할 categoryTitles 상태와 해당 상태를 변경하는 setCategoryTitles 함수를 정의한다. 초기값은 위와 같은 객체로 선언한다. 이때 미리 정의해둔 상수값을 사용하여 프로퍼티 이름을 설정한다. 

<Tab.Screen name="Home" children={(props) => <HomeScreen 
                                                        {...props} 
                                                        caretType={caretType} 
                                                        setCaretType={setCaretType} 
                                                        todos={todos} loading={loading} 
                                                        setNumOfTodosToday={setNumOfTodosToday} 
                                                        userInfo={userInfo} 
                                                        setCategoryTitles={setCategoryTitles} // 추가
                                                        categoryTitles={categoryTitles} // 추가
                                                        CATEGORY_DROPDOWN_TITLE={CATEGORY_DROPDOWN_TITLE} // 추가
                                                        />} options={{
          title: '홈',
          tabBarIcon: ({ color, size }) => <Icon name="home" color={color} size={size}/>,
          headerTitle: (props) => <DropdownCategory {...props} caretType={caretType} setCaretType={setCaretType} categoryTitle={categoryTitles[CATEGORY_DROPDOWN_TITLE] ? categoryTitles[CATEGORY_DROPDOWN_TITLE] : CATEGORY_DROPDOWN_TITLE}/>,  // 수정
          headerStyle: {
            backgroundColor: '#a8c8ffff',
          },
          headerTitleStyle: {
            fontWeight: 'bold',
            color: '#fff'
          },
          tabBarBadge: numOfTodosToday
}}/>

드롭다운 타이틀을 관리하는 state (categoryTitles)와 state 를 변경하는 함수(setCategoryTitles)를 HomeScreen 컴포넌트의 props 로 넘겨준다. DropdownCategory 컴포넌트의 categoryTitle 속성은 기존처럼 "카테고리"라는 문자열로 하드코딩하지 않고, categoryTitles["카테고리"] 값이 존재하면 해당값으로 드롭다운 버튼이름을 설정하고, 그렇지 않으면 "카테고리" 라는 문자열로 설정한다. categoryTitles["카테고리"] 값은 카테고리 드롭다운 메뉴에서 특정 카테고리를 선택하면 해당 카테고리명으로 변경된다. CATEGORY_DROPDOWN_TITLE 상수는 사용자가 홈화면에서 드롭다운 메뉴중 특정 아이템을 선택할때 해당 드롭다운의 버튼이름이 사용자가 선택한 아이템으로 변경되어야 하는데, 이때 전체 드롭다운 버튼중 어느 드롭다운 버튼의 이름을 변경해야 할지 선택할때 사용된다. 마치 리액트에서 입력창이 여러개 있을때 [e.target.name]: e.target.value 처럼 특정 입력창을 선택하는 것과 같은 원리이다. 

<Tab.Screen name="Calendar" children={(props) => <CalendarScreen 
                                                          {...props}
                                                          yearCaret={yearCaret}
                                                          setYearCaret={setYearCaret}
                                                          monthCaret={monthCaret}
                                                          setMonthCaret={setMonthCaret}
                                                          setCategoryTitles={setCategoryTitles} // 추가
                                                          categoryTitles={categoryTitles} // 추가
                                                          YEAR_DROPDOWN_TITLE={YEAR_DROPDOWN_TITLE} // 추가
                                                          MONTH_DROPDOWN_TITLE={MONTH_DROPDOWN_TITLE} // 추가
                                                          />} options={{
          title: '달력',
          tabBarIcon: ({ color, size }) => <Icon name="calendar-month" color={color} size={size}/>,
          headerTitle: (props) => (<View style={{flexDirection: 'row'}}>
            <DropdownCategory {...props} caretType={yearCaret} setCaretType={setYearCaret} categoryTitle={categoryTitles[YEAR_DROPDOWN_TITLE] ? categoryTitles[YEAR_DROPDOWN_TITLE] : YEAR_DROPDOWN_TITLE}/> // 추가
            <DropdownCategory {...props} caretType={monthCaret} setCaretType={setMonthCaret} categoryTitle={categoryTitles[MONTH_DROPDOWN_TITLE] ? categoryTitles[MONTH_DROPDOWN_TITLE] : MONTH_DROPDOWN_TITLE}/> // 추가
          </View>),
          headerStyle: {
            backgroundColor: '#a8c8ffff',
          },
          headerTitleStyle: {
            fontWeight: 'bold',
            color: '#fff'
          },
        }}/>

드롭다운 타이틀을 관리하는 state (categoryTitles)와 state 를 변경하는 함수(setCategoryTitles)를 CalendarScreen 컴포넌트의 props 로 넘겨준다. DropdownCategory 컴포넌트의 categoryTitle 속성은 기존처럼 "Year"라는 문자열로 하드코딩하지 않고, categoryTitles["년"] 값이 존재하면 해당값으로 드롭다운 버튼이름을 설정하고, 그렇지 않으면 "년" 이라는 문자열로 설정한다. categoryTitles["년"] 값은 년도를 선택하는 드롭다운 메뉴에서 특정 년도를 선택하면 해당년도로 변경된다. categoryTitles["월"]도 마찬가지 원리이다. YEAR_DROPDOWN_TITLE 상수는 사용자가 캘린더 화면에서 드롭다운 메뉴중 특정 년도를 선택할때 해당 드롭다운의 버튼이름이 사용자가 선택한 년도로 변경되어야 하는데, 이때 전체 드롭다운 버튼중 어느 드롭다운 버튼의 이름을 변경해야 할지 선택할때 사용된다. 마치 리액트에서 입력창이 여러개 있을때 [e.target.name]: e.target.value 처럼 특정 입력창을 선택하는 것과 같은 원리이다.

 

import React, { useState, useEffect, useRef } from 'react' // 카테고리 저장을 위한 useRef 임포트(수정)
import { 
  addData,
  removeData,
  getCurrentTime,
  // getCollection // 주석처리
} from '../apis/firebase'

import { // 오늘과 내일 날짜기준을 계산하는 유틸리티 함수
  getToday,
  getTomorrow,
  getTodosToday,
  getTodosBySpecificDate
} from '../utils/time' 

import { 
  SafeAreaView, 
  View, Text, 
  StyleSheet, 
  StatusBar, 
  Keyboard, 
  FlatList,
  TouchableHighlight,
    Modal, Pressable
} from 'react-native'

import DateHeader from '../components/DateHeader'
import Default from '../components/Default'
import TodoInsert from '../components/TodoInsert'
import TodoList from '../components/TodoList'
import DropdownList from '../components/DropdownList'

function HomeScreen({ navigation, caretType, setCaretType, todos, loading, route, setNumOfTodosToday, userInfo, setCategoryTitles, categoryTitles, CATEGORY_DROPDOWN_TITLE }){ // 필요한 데이터 추가 (todos, loading, route)
  const categories = ['자기계발', '업무', '오락', '여행', '연애', 'IT', '취미']
  const [todoText, setTodoText] = useState('')
  const [warning, setWarning] = useState(false)
  const [modalOpen, setModalOpen] = useState(false)
  const [todoToRemove, setTodoToRemove] = useState({id: null, title: ''})

  // 오늘/내일의 날짜를 기준으로 할일목록을 필터링하고 정렬함
  const category = useRef('') // 카테고리 변수
  const date = (route.params && route.params.date) ? new Date(route.params.date) : new Date()
  const {todosToday, today} = getTodosBySpecificDate(date, todos) // dateOfTodo 기준으로 필터링
  const todosTodayLatest = [...todosToday] // 원본복사
  todosTodayLatest.sort((a, b) => b.createdAt?.seconds - a.createdAt?.seconds) // 최신순 정렬 (업데이트되는 시간차 때문에 createdAt 이 null 일수 있음)

  console.log("현재 선택날짜: ", date, todosToday)
  console.log("날짜비교: ", date.getTime(), today.getTime() < getToday(new Date()).getTime())

  const onInsertTodo = async (trimedText) => {
    if(!category.current){ // 카테고리를 선택하지 않은 경우
      setTodoText('카테고리를 먼저 선택해주세요!')
      setWarning(true)
      return 
    }
    if(trimedText && trimedText.length > 3){ // 최소 글자수 제한
      if(todos.filter(todo => todo.title === trimedText).length > 0){
        setTodoText('중복된 할일입니다.')
        setWarning(true)
      }else{
        const newTodo = {
          title: trimedText,
          category: category.current || '자기계발', // 선택한 카테고리 설정 (수정)
          isDone: false,
          createdAt: getCurrentTime(), // 클라이언트 기준이 아니라 서버기준 저장시각,
          dateOfTodo: date, // createdAt 기준으로 todo 를 필터링하는게 아니라 해당 필드 기준으로 걸러냄
          userEmail: userInfo.email // 할일을 생성한 사용자 필드 추가 
        }
        await addData('todos', newTodo)
        Keyboard.dismiss() // 추가버튼 클릭시 키보드 감추기 
        setTodoText('') // 입력창 초기화
        category.current = '' // 카테고리 초기화 (추가)
        setCategoryTitles({...categoryTitles, [CATEGORY_DROPDOWN_TITLE]: null})
      }
    }else{
      console.log('3자 이상 입력하세요!')
      setTodoText('3자 이상 입력하세요!')
      setWarning(true)
    }
  }

  const closeDropdown = () => {
    caretType && setCaretType(false)
  }
  const selectCategory = (item, e) => { // 카테고리 드롭다운 선택시 (추가)
    console.log("카테고리: ", item)
    closeDropdown()
    category.current = item 
    setCategoryTitles({...categoryTitles, [CATEGORY_DROPDOWN_TITLE]: item})
  }
  const handleOutSideOfMenu = (e) => {
    console.log('홈화면을 터치하셨습니다.')
    closeDropdown()
  }
  const removeTodo = (id, title) => {
    setModalOpen(true)
    setTodoToRemove({id, title})
    console.log(`할일 [${title}] 제거`)
  }
  const handleRemove = () => {
    setModalOpen(false)
    setTodoToRemove({id: null, title: ''})
    removeData('todos', todoToRemove.id)
  }

  useEffect(() => navigation.addListener('focus', () => console.log('페이지 로딩')), [])

  useEffect(() => navigation.addListener('blur', () => console.log('페이지 벗어남')), [])

  useEffect(() => {
    setNumOfTodosToday(todosToday.length)
  })

  return (
    <SafeAreaView 
        style={styles.block} 
        onTouchStart={handleOutSideOfMenu}>
      <StatusBar backgroundColor="#a8c8ffff"></StatusBar>
      <Modal
        animationType="fade"
        transparent={true}
        visible={modalOpen}
        onRequestClose={() => {
          Alert.alert('Modal has been closed.');
          setModalOpen(!modalOpen);
        }}
      >
        <View style={styles.centeredView}>
          <View style={styles.modalView}>
            <Text style={styles.guideText}>할일 "{todoToRemove.title}" 을 제거하시겠습니까?</Text>
            <View style={styles.alignHorizontal}>
              <Pressable
                style={[styles.button, styles.buttonClose, styles.remove]}
                onPress={handleRemove}>
                <Text style={styles.textStyle}>삭제</Text>
              </Pressable>
              <Pressable
                style={[styles.button, styles.buttonClose]}
                onPress={() => setModalOpen(false)}>
                <Text style={styles.textStyle}>닫기</Text>
              </Pressable>
            </View>
          </View>
        </View>
      </Modal>
      
        {caretType && <DropdownList categories={categories} selectCategory={selectCategory} top={-15}/>}
        <DateHeader date={date}/>
        {/* 해당날짜 기준 최신순으로 정렬된 할일목록 */}
        {todosTodayLatest.length === 0 ? 
            <Default/> : 
            <TodoList todos={todosTodayLatest} removeTodo={removeTodo}
        />}
        {/* 필터링된 할일목록의 날짜와 현재 날짜가 동일하지 않은 경우 */}
        <TodoInsert 
          onInsertTodo={onInsertTodo} 
          todoText={todoText} 
          setTodoText={setTodoText} 
          warning={warning} 
          setWarning={setWarning}
          disabled={today.getTime()<getToday(new Date()).getTime()}/> 
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  block: {
    flex: 1,
  },
  centeredView: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 22,
  },
  modalView: {
    margin: 50,
    backgroundColor: 'white',
    borderRadius: 20,
    padding: 20,
    alignItems: 'center',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
  },
  alignHorizontal: {
    flexDirection: 'row',
    justifyContent: 'flex-end'
  },
  guideText: {
    fontWeight: 'bold',
    fontSize: 15
  },
  button: {
    width: 70,
    height: 40,
    borderRadius: 10,
    padding: 0,
    elevation: 2,
    marginTop: 30,
    marginRight: 5,
    justifyContent: 'center'
  },
  buttonOpen: {
    backgroundColor: '#F194FF',
  },
  buttonClose: {
    backgroundColor: '#a8c8ffff',
  },
  textStyle: {
    color: 'white',
    fontWeight: 'bold',
    textAlign: 'center',
  },
  remove: {
    backgroundColor: 'red'
  },
  modalText: {
    marginBottom: 15,
    textAlign: 'center',
  },
})
export default HomeScreen

screens > HomeScreen.js 파일을 위와 같이 수정한다.

function HomeScreen({ navigation, caretType, setCaretType, todos, loading, route, setNumOfTodosToday, userInfo, setCategoryTitles, categoryTitles, CATEGORY_DROPDOWN_TITLE }){ // 필요한 데이터 추가 (setCategoryTitles, categoryTitles, CATEGORY_DROPDOWN_TITLE)
 // 중략
}

App 컴포넌트로부터 추가적으로 전달받은 3개의 props 를 조회한다.

if(todos.filter(todo => todo.title === trimedText).length > 0){
        setTodoText('중복된 할일입니다.')
        setWarning(true)
      }else{
        const newTodo = {
          title: trimedText,
          category: category.current || '자기계발', // 선택한 카테고리 설정 (수정)
          isDone: false,
          createdAt: getCurrentTime(), // 클라이언트 기준이 아니라 서버기준 저장시각,
          dateOfTodo: date, // createdAt 기준으로 todo 를 필터링하는게 아니라 해당 필드 기준으로 걸러냄
          userEmail: userInfo.email // 할일을 생성한 사용자 필드 추가 
        }
        await addData('todos', newTodo)
        Keyboard.dismiss() // 추가버튼 클릭시 키보드 감추기 
        setTodoText('') // 입력창 초기화
        category.current = '' // 카테고리 초기화 (추가)
        setCategoryTitles({...categoryTitles, [CATEGORY_DROPDOWN_TITLE]: null}) // 드롭다운 버튼이름 초기화
      }

할일이 파이어베이스 데이터베이스에 성공적으로 추가되면 홈화면에 있는 카테고리 드롭다운 버튼의 이름을 "카테고리"로 초기화한다. 

const selectCategory = (item, e) => { // 카테고리 드롭다운 선택시 (추가)
    console.log("카테고리: ", item)
    closeDropdown()
    category.current = item 
    setCategoryTitles({...categoryTitles, [CATEGORY_DROPDOWN_TITLE]: item})
  }

사용자가 할일을 추가하기 위하여 드롭다운 메뉴에서 특정 카테고리를 선택하면 categoryTitles["카테고리"]의 값은 해당 카테고리명으로 설정된다. 즉, 드롭다운 버튼의 이름이 사용자가 선택한 카테고리명으로 설정된다. 

import React, { useState, useCallback } from 'react'
import { 
  SafeAreaView, 
  View, Text, 
  StyleSheet, 
  StatusBar, 
  Button,
  FlatList,
  Dimensions,
  TouchableWithoutFeedback
} from 'react-native'
import { getFullCalendar } from '../utils/time'

import DropdownList from '../components/DropdownList'

function CalendarScreen({navigation, yearCaret, setYearCaret, monthCaret, setMonthCaret, setCategoryTitles, categoryTitles, YEAR_DROPDOWN_TITLE, MONTH_DROPDOWN_TITLE }){
  const today = getFullCalendar(new Date())
  const week = ["일", "월", "화", "수", "목", "금", "토"] //일주일
  const [selectedYear, setSelectedYear] = useState(today.year) //현재 선택된 연도
  const [selectedMonth, setSelectedMonth] = useState(today.month) //현재 선택된 달

  const N = 10 // +-10년 범위
  const offset = today.year - N
  const yearsRange = Array(2*N).fill(0).map((_, id) => `${id+offset}년`)
  const monthRange = Array(12).fill(0).map((_, id) => `${id+1}월`)

  const daysOfMonth = new Date(selectedYear, selectedMonth, 0).getDate() //선택된 연도, 달의 마지막 날짜
  const day = new Date(selectedYear, selectedMonth - 1, 1).getDay() // 첫째날 요일
  const lastDay = new Date(selectedYear, selectedMonth - 1, daysOfMonth).getDay() // 마지막날 요일
  const days = [...Array(day).fill(""), ...Array(daysOfMonth).fill(0).map((_, id) => id+1), ...Array(week.length-(lastDay+1)).fill("")]
  console.log(daysOfMonth, selectedYear, selectedMonth, day, week[day], lastDay, week[lastDay])
  console.log(days, days.length)

  const prevMonth = useCallback(() => { //이전 달 보기 보튼
    setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: null, [MONTH_DROPDOWN_TITLE]: null})
    if (selectedMonth === 1) {
      setSelectedMonth(12)
      setSelectedYear(selectedYear - 1)
    } else {
      setSelectedMonth(selectedMonth - 1)
    }
  }, [selectedMonth])
  const nextMonth = useCallback(() => { //다음 달 보기 버튼
    setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: null, [MONTH_DROPDOWN_TITLE]: null})
    if (selectedMonth === 12) {
      setSelectedMonth(1)
      setSelectedYear(selectedYear + 1)
    } else {
      setSelectedMonth(selectedMonth + 1)
    }
  }, [selectedMonth])
  
  const selectCategory = (item, e) => { // 날짜 드롭다운 선택시 (추가)
    console.log("날짜: ", item)
    const lastChr = item[item.length-1]
    if(lastChr === "년"){
      setSelectedYear(parseInt(item))
      setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: item})
    }else if(lastChr === "월"){
      setSelectedMonth(parseInt(item))
      setCategoryTitles({...categoryTitles, [MONTH_DROPDOWN_TITLE]: item})
    }
    closeDropdown()
  }
  const closeDropdown = () => {
    yearCaret && setYearCaret(false)
    monthCaret && setMonthCaret(false)
  }
  const handleOutSideOfMenu = (e) => {
    console.log('캘린더 화면을 터치하셨습니다.')
    closeDropdown()
  }
  const setDate = (selectedDate) => {
    console.log(`${selectedYear}-${selectedMonth}-${selectedDate}`)
    navigation.navigate('Home', { date: `${selectedYear}-${selectedMonth}-${selectedDate}` })
  }

  return (
    <SafeAreaView 
      style={styles.block}
      onTouchStart={handleOutSideOfMenu}>
      <StatusBar backgroundColor="#a8c8ffff"></StatusBar>
      {yearCaret && <DropdownList categories={yearsRange} top={-15} rate={2/3} selectCategory={selectCategory}/>}
      {monthCaret && <DropdownList categories={monthRange} top={-15} left={70} selectCategory={selectCategory}/>}
      
      <View style={styles.calendarContainer}>
        <View style={styles.calendarHeader} onTouchStart={(e) => { // 터치 시작점 설정 : 캡쳐링 방지 (추가)
                console.log('캘린더 이전/다음')
                // e.stopPropagation() 버블링 허용 (해당 버튼 클릭전 드롭다운 닫기)
              }}>
          <Button title="◀︎" onPress={prevMonth}></Button>
          <Text style={styles.calendarHeaderText}>{selectedYear}년 {selectedMonth}월</Text>
          <Button title="▶︎" onPress={nextMonth}></Button>
        </View>
        
        <FlatList
              data={week} 
              keyExtractor={item => item}
              renderItem={({item}) => (
                <View style={styles.day}>
                  <Text>{item}</Text>
                </View>
              )}
              numColumns={7}
              horizontal={false}
            />
          <FlatList
              data={days} 
              keyExtractor={item => item}
              renderItem={({item}) => (
                <View style={[
                                styles.day, 
                                (selectedYear === today.year && selectedMonth === today.month && item === today.date) && styles.today
                            ]} onTouchStart={(e) => { // 터치 시작점 설정 : 캡쳐링 방지 (추가)
                  console.log('날짜선택 이벤트')
                  // e.stopPropagation() // 버블링 허용 (해당 버튼 클릭전 드롭다운 닫기)
                  setDate(item)
                }}>
                    <Text style={[
                      styles.weekday, 
                      new Date(selectedYear, selectedMonth - 1, item).getDay() === 0 && styles.sunday, 
                      new Date(selectedYear, selectedMonth - 1, item).getDay() === 6 && styles.saturday]}>{item}</Text>
                </View>
              )}
              numColumns={7}
              horizontal={false}
              contentContainerStyle={{justifyContent: 'flex-start'}}
            />
        
      </View>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  block: {
    flex: 1,
  },
  calendarContainer: {
    width: Dimensions.get('window').width * 0.9, // 80%
    backgroundColor: '#777', 
    marginTop: 20,
    marginLeft: 'auto',
    marginRight: 'auto',
  },
  calendarHeader: {
    flexDirection: 'row'
  },
  calendarHeaderText: {
    flex: 1,
    justifyContent: 'center',
    textAlign: 'center',
    backgroundColor: '#a8c8ffff',
    color: '#fff',
    fontSize: 20,
    fontWeight: 'bold'
  },
  day: {
    backgroundColor: '#fff',
    margin: .2,
    flex: 1,
    alignItems: 'center',
    padding: 3,
  },
  today: {backgroundColor:  '#a8c9ffff'},
  weekday: {color: '#333'},
  sunday: {color: '#de1738'},
  saturday: {color: '#4169e1'}
})
export default React.memo(CalendarScreen)

screens > CalendarScreen.js 파일을 위와 같이 수정한다. 

function CalendarScreen({navigation, yearCaret, setYearCaret, monthCaret, setMonthCaret, setCategoryTitles, categoryTitles, YEAR_DROPDOWN_TITLE, MONTH_DROPDOWN_TITLE }){
 // 중략
}

setCategoryTitles, categoryTitles, YEAR_DROPDOWN_TITLE, MONTH_DROPDOWN_TITLE 를 추가적으로 임포트한다.

const prevMonth = useCallback(() => { //이전 달 보기 보튼
    setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: null, [MONTH_DROPDOWN_TITLE]: null}) // 추가
    if (selectedMonth === 1) {
      setSelectedMonth(12)
      setSelectedYear(selectedYear - 1)
    } else {
      setSelectedMonth(selectedMonth - 1)
    }
  }, [selectedMonth])

이전달 보기버튼을 클릭하면 년도/월을 선택하는 드롭다운 버튼이름이 "년", "월" 로 각각 초기화된다. 

const nextMonth = useCallback(() => { //다음 달 보기 버튼
    setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: null, [MONTH_DROPDOWN_TITLE]: null}) // 추가
    if (selectedMonth === 12) {
      setSelectedMonth(1)
      setSelectedYear(selectedYear + 1)
    } else {
      setSelectedMonth(selectedMonth + 1)
    }
  }, [selectedMonth])

다음달 보기버튼을 클릭하면 년도/월을 선택하는 드롭다운 버튼이름이 "년", "월" 로 각각 초기화된다. 

const selectCategory = (item, e) => { // 날짜 드롭다운 선택시 (추가)
    console.log("날짜: ", item)
    const lastChr = item[item.length-1]
    if(lastChr === "년"){
      setSelectedYear(parseInt(item))
      setCategoryTitles({...categoryTitles, [YEAR_DROPDOWN_TITLE]: item}) // 추가
    }else if(lastChr === "월"){
      setSelectedMonth(parseInt(item))
      setCategoryTitles({...categoryTitles, [MONTH_DROPDOWN_TITLE]: item}) // 추가
    }
    closeDropdown()
  }

년도를 선택하는 드롭다운 메뉴에서 사용자가 특정 년도를 선택하면 categoryTitles["년"] 값이 해당 년도로 설정된다. 즉, 년도에 대한 드롭다운 버튼이름이 선택한 년도로 설정된다. 월을 선택하는 드롭다운 메뉴도 마찬가지 원리이다. 이때 YEAR_DROPDOWN_TITLE, MONTH_DROPDOWN_TITLE 은 문자열 형태이므로 객체의 프로퍼티 값을 변경하려면 대괄호([])로 감싸줘야 한다. 그렇지 않으면 categoryTitles 객체의 YEAR_DROPDOWN_TITLE이라는 프로퍼티 이름으로 값이 설정된다. 즉, "년"이라는 프로퍼티에 값이 설정되지 않는다. 

 

카테고리 드롭다운 버튼이름 변경
년/월 드롭다운 버튼이름 변경

 

 

 

728x90