React Native Calendar Agenda组件切换至过去日期时数据异常求助
React Native Agenda组件切换至过去日期时数据异常问题解决
问题描述
使用react-native-calendars的Agenda组件展示用户预约信息、标记有预约的日期时,从存在多个预约的日期切换到过去日期,数据会出现异常变化。
组件代码
/* eslint-disable no-nested-ternary */ import { useLazyQuery, useQuery } from "@apollo/client"; import dayjs from "dayjs"; import React, { useContext, useState } from "react"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { Agenda } from "react-native-calendars"; import { useDispatch } from "react-redux"; import { UserContext } from "../context/AuthContextProvider"; import { GET_USER_APPOINTMENTS } from "../graphql/query"; import testIDs from "../testIDs"; import { addMissingDates, parseAppointmentData, } from "../utils/calendarDataParser"; import { leadSelected } from "../reducers/leadSlice"; import { appointmentSelected } from "../reducers/appointmentSlice"; function AppointmentCalendar({ route, navigation }) { const [items, setItems] = useState({}); const { authUser } = useContext(UserContext); const [variables, setVariables] = useState({ userIds: [authUser?.id], startDate: dayjs()?.startOf("month")?.format("YYYY-MM-DD"), endDate: dayjs()?.endOf("month")?.format("YYYY-MM-DD"), }); const dispatch = useDispatch(); const { loading: appointmentLoader } = useQuery(GET_USER_APPOINTMENTS, { variables: { ...variables }, onCompleted: (res) => { setItems((prev) => ({ ...prev, ...addMissingDates( parseAppointmentData(res?.getUserAppointment?.data), dayjs().format("YYYY-MM-DD") ), })); }, }); const loadItems = (day) => { setVariables({ ...variables, startDate: dayjs(day.dateString)?.startOf("month")?.format("YYYY-MM-DD"), endDate: dayjs(day.dateString)?.endOf("month")?.format("YYYY-MM-DD"), }); }; const renderEmptyDate = () => ( <View style={styles?.emptyDate}> <Text>No appointment scheduled!</Text> </View> ); const RenderItem = React.memo((props) => { const { reservation, isFirst, dispatch, navigation, route, styles } = props; const fontSize = isFirst ? 16 : 14; const color = isFirst ? "black" : "#43515c"; const appointmentColor = reservation.appointmentStatus === "SCHEDULED" ? "#1890ff" : reservation.appointmentStatus === "RESCHEDULED" ? "#13c2c2" : reservation.appointmentStatus === "CANCELLED" ? "#f5222d" : "#52c41a"; return ( <TouchableOpacity testID={testIDs.agenda.ITEM} style={[ styles.item, { height: reservation.height, backgroundColor: appointmentColor }, ]} onPress={() => { navigation.navigate("EditAppointment", { ...route?.params, mode: "EDIT", }); dispatch(appointmentSelected(reservation?.appointment)); }} > <Text style={{ fontSize, color: "#fff" }}>{reservation?.name}</Text> </TouchableOpacity> ); }, []); const rowHasChanged = (r1, r2) => r1?.appointment?.id !== r2?.appointment?.id; const timeToDate = (time) => { const date = new Date(time); return date.toISOString().split("T")[0]; }; const timeToString = (time) => { const date = new Date(time); return dayjs(date)?.format("hh:mm A"); }; return ( <View style={styles?.container}> <Agenda testID={testIDs?.agenda?.CONTAINER} items={items} loadItemsForMonth={loadItems} selected={new Date()} onDayChange={(day) => console.log(day.dateString)} renderItem={(reservation, isFirst) => ( <RenderItem isFirst={isFirst} key={reservation?.appointment?.id} reservation={reservation} dispatch={dispatch} navigation={navigation} route={route} styles={styles} /> )} renderEmptyDate={renderEmptyDate} rowHasChanged={rowHasChanged} showClosingKnob /> </View> ); } export default AppointmentCalendar; const styles = StyleSheet.create({ container: { flex: 1, // backgroundColor: "#a9addf", }, boxContainer: { backgroundColor: "#a9addf", paddingTop: 10, paddingBottom: 10, }, listItem: { marginTop: 5, padding: 10, backgroundColor: "#FFF", width: "98%", flex: 1, alignSelf: "center", flexDirection: "row", borderRadius: 5, }, viewRow: { flex: 1, flexDirection: "row", }, textGray: { color: "gray", fontSize: 14, marginBottom: 2, }, textCapitalGray: { color: "gray", fontSize: 16, marginBottom: 2, }, item: { backgroundColor: "white", flex: 1, borderRadius: 5, padding: 10, marginRight: 10, marginTop: 17, }, emptyDate: { height: 15, flex: 1, paddingTop: 30, }, });
辅助函数代码
export const parseAppointmentData = (input) => { try { const systemTimezone = dayjs?.tz?.guess(); const output = {}; input?.forEach((item) => { const date = dayjs(item?.startDatetime) ?.tz(systemTimezone) ?.format("YYYY-MM-DD"); const time = dayjs(item?.startDatetime) ?.tz(systemTimezone) ?.format("HH:mm A"); const name = `${time} - ${item?.summary}`; if (!output[date]) { output[date] = []; } output[date]?.push({ name, height: 50, day: date, appointmentStatus: item?.appointmentStatus, appointment: item, }); }); return output; } catch (err) { console?.log(err); } }; export function addMissingDates(json, date) { const systemTimezone = dayjs?.tz?.guess(); const missingDates = []; // Get the starting date of the current month const startingDate = dayjs(date)?.tz(systemTimezone)?.startOf("month"); // Get the ending date of the current month const endingDate = dayjs(date)?.tz(systemTimezone)?.endOf("month"); // Initialize the current date with the starting date let currentDate = startingDate; const datesWithAppointments = Object?.keys(json); // Loop until the current date reaches the ending date while (currentDate?.isSameOrBefore(endingDate, "day")) { // Output the current date if (!datesWithAppointments?.includes(currentDate?.format("YYYY-MM-DD"))) { missingDates?.push(currentDate?.format("YYYY-MM-DD")); } // Increment the current date by one day currentDate = currentDate?.add(1, "day"); } missingDates?.forEach((missedDate) => { const formattedDate = missedDate; json[formattedDate] = []; }); return json; }
问题根源
- 数据合并逻辑错误:
onCompleted中使用...prev合并新老数据,导致切换月份时旧月份的预约数据不会被清除,新数据与旧数据混杂引发显示异常。 - 补全空日期参数错误:调用
addMissingDates时传入的是当前日期,而非正在加载的目标月份日期,导致补全的是空日期为当前月份的,而非目标月份的,进一步加剧数据混乱。
解决方案
修改1:重置数据而非合并
在useQuery的onCompleted回调中,直接用新请求的处理后数据替换items,不再保留旧数据:
onCompleted: (res) => { // 基于当前请求的月份日期补全空日期 const targetMonthDate = variables.startDate; const parsedData = parseAppointmentData(res?.getUserAppointment?.data); const newItems = addMissingDates(parsedData, targetMonthDate); // 直接替换items,而非合并旧数据 setItems(newItems); },
修改2:确保补全空日期的参数正确性
通过variables.startDate传入目标月份的起始日期,保证addMissingDates补全的是当前加载月份的所有空日期。
完整修改后的组件关键部分代码
const { loading: appointmentLoader } = useQuery(GET_USER_APPOINTMENTS, { variables: { ...variables }, onCompleted: (res) => { const targetMonthDate = variables.startDate; const parsedData = parseAppointmentData(res?.getUserAppointment?.data); const newItems = addMissingDates(parsedData, targetMonthDate); setItems(newItems); }, });
额外优化建议
- 添加月份缓存逻辑,避免重复请求相同月份的数据,提升性能。
- 在
loadItems中添加防抖处理,避免快速切换月份时频繁发起请求。
内容的提问来源于stack exchange,提问作者Siddhrajsinh Gohil
相关产品推荐
相关产品推荐

