You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native演出指南APP:切换日期展示演出功能的实现问题

React Native演出指南APP日期切换问题解决方案

我正在开发一款React Native演出指南APP,Firebase集合里的每个演出文档都带date属性(格式为DD/MM/YYYY)。需求是默认展示当日演出,支持用户点击「前日演出」「次日演出」按钮切换对应日期的演出。

目前我能通过以下逻辑获取当日演出:

const day = new Date().getDate();
const month = new Date().getMonth() + 1;
const year = new Date().getFullYear();
const dateToday = `${day}/${month}/${year}`;

// 筛选当日演出
const gigsToday = gigs.filter((gig) => gig.date === dateToday);

但在实现日期切换时遇到了问题:

  • 初始日期状态无法即时加载,导致渲染内容不符合日期条件
  • 不确定操作的日期该设为普通变量还是状态变量
  • 已尝试的日期切换逻辑存在错误,无法正确展示次日演出

我已经做了这些尝试:

  • 将new Date()转换为UTC+13时间:
const addHours = (numOfHours, date = new Date()) => {
  date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
  return date;
};

let localDate = addHours(13);
  • 给「次日演出」按钮绑定onPress事件,尝试更新日期状态:
<Pressable onPress={addDay}>
  <Text style={styles.buttonOptionsText}>next day's gigs</Text>
</Pressable>
  • 写了addDay函数尝试修改日期:
const [date, setDate] = useState(dateToday);
const [daysAdded, setDaysAdded] = useState(1);

const addDay = () => {
  setDaysAdded(daysAdded + 1);
  localDate.setDate(localDate.getDate() + daysAdded);
  setDate(localDate);
};

以下是完整代码:

GigMap.js

import { useState, useEffect } from "react";
import { StyleSheet, Text, View, Pressable } from "react-native";
import MapView from "react-native-maps";
import { Marker, Callout } from "react-native-maps";
import CalloutView from "./CalloutView";
import { mapStyle } from "../util/mapStyle";
import { useGigs } from "../hooks/useGigs";

const GigMap = ({ navigation }) => {

  const [date, setDate] = useState(dateToday);
  const gigs = useGigs()
  const [daysAdded, setDaysAdded] = useState(1);

  const addHours = (numOfHours, date = new Date()) => {
    date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);
    return date;
  };

  let localDate = addHours(13);

  useEffect(() => {
    setDate(localDate);
  }, []);

  const addDay = () => {
    setDaysAdded(daysAdded + 1);
    localDate.setDate(localDate.getDate() + daysAdded);
    setDate(localDate);
  };

  // 生成当前日期
  const day = new Date().getDate();
  const month = new Date().getMonth() + 1;
  const year = new Date().getFullYear();
  const dateToday = `${day}/${month}/${year}`;

  // 筛选当日演出
  const gigsToday = gigs.filter((gig) => gig.date === dateToday);

  return (
    <View style={styles.container}>
      <Text style={styles.headerText}>Today's gigs</Text>
      <MapView
        initialRegion={{
          latitude: -41.29416,
          longitude: 174.77782,
          latitudeDelta: 0.03,
          longitudeDelta: 0.03,
        }}
        style={styles.map}
        customMapStyle={mapStyle}
      >
        {gigsToday.map((gig, i) => (
          <Marker
            key={i}
            coordinate={{
              latitude: gig.location.latitude,
              longitude: gig.location.longitude,
            }}
            image={require("../assets/Icon_Gold_48x48.png")}
          >
            <Callout
              style={styles.callout}
              onPress={() =>
                navigation.navigate("GigDetails", {
                  venue: gig.venue,
                  date: gig.date,
                  gigName: gig.gigName,
                  time: gig.time,
                })
              }
            >
              <CalloutView
                venue={gig.venue}
                date={gig.date}
                gigName={gig.gigName}
                time={gig.time}
                style={styles.calloutView}
              />
            </Callout>
          </Marker>
        ))}
      </MapView>
      <View style={styles.buttonOptions}>
        <Pressable>
          <Text style={styles.buttonOptionsText}>previous day's gigs</Text>
        </Pressable>
        <Pressable onPress={addDay}>
          <Text style={styles.buttonOptionsText}>next day's gigs</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "column",
    alignItems: "center",
  },
  map: {
    height: 500,
    width: 330,
    margin: 10,
  },
  headerText: {
    color: "black",
    fontSize: 20,
    marginTop: 5,
  },
  callout: {
    width: 200,
    height: 100,
  },
  buttonOptions: {
    flexDirection: "row",
    justifyContent: "flex-start",
  },
  buttonOptionsText: {
    margin: 5,
  },
});

export default GigMap;

问题分析与解决方案

核心问题

  1. 日期状态管理混乱:同时使用dateToday普通变量和date状态变量,且初始状态赋值错误(组件初始化时dateToday还未定义)。
  2. 日期操作逻辑错误:addDay函数中直接修改localDate(组件渲染时创建的可变变量),并依赖daysAdded状态做累加,导致日期计算错误且无法触发正确重渲染。
  3. 日期格式不统一:状态中存储的是Date对象,但筛选时用的是字符串格式,无法匹配。

修正后的代码实现

import { useState, useEffect } from "react";
import { StyleSheet, Text, View, Pressable } from "react-native";
import MapView from "react-native-maps";
import { Marker, Callout } from "react-native-maps";
import CalloutView from "./CalloutView";
import { mapStyle } from "../util/mapStyle";
import { useGigs } from "../hooks/useGigs";

// 工具函数:将Date对象转换为DD/MM/YYYY格式的字符串
const formatDate = (date) => {
  const day = date.getDate();
  const month = date.getMonth() + 1;
  const year = date.getFullYear();
  return `${day}/${month}/${year}`;
};

// 工具函数:添加指定小时数到日期(复制原对象避免副作用)
const addHours = (numOfHours, date = new Date()) => {
  const newDate = new Date(date);
  newDate.setTime(newDate.getTime() + numOfHours * 60 * 60 * 1000);
  return newDate;
};

const GigMap = ({ navigation }) => {
  // 状态存储当前选中的日期(Date对象)
  const [currentDate, setCurrentDate] = useState(null);
  const gigs = useGigs();

  // 初始化日期:设置为UTC+13的当日
  useEffect(() => {
    const initialDate = addHours(13);
    setCurrentDate(initialDate);
  }, []);

  // 切换日期:增加/减少n天
  const adjustDate = (days) => {
    if (!currentDate) return;
    const newDate = new Date(currentDate);
    newDate.setDate(newDate.getDate() + days);
    setCurrentDate(newDate);
  };

  // 筛选当前日期的演出
  const filteredGigs = currentDate 
    ? gigs.filter((gig) => gig.date === formatDate(currentDate))
    : [];

  // 生成显示用的日期文本
  const displayDateText = currentDate 
    ? `${formatDate(currentDate)} 的演出`
    : "加载中...";

  return (
    <View style={styles.container}>
      <Text style={styles.headerText}>{displayDateText}</Text>
      <MapView
        initialRegion={{
          latitude: -41.29416,
          longitude: 174.77782,
          latitudeDelta: 0.03,
          longitudeDelta: 0.03,
        }}
        style={styles.map}
        customMapStyle={mapStyle}
      >
        {filteredGigs.map((gig, i) => (
          <Marker
            key={gig.id || i} // 优先用文档ID作为key,避免索引key的问题
            coordinate={{
              latitude: gig.location.latitude,
              longitude: gig.location.longitude,
            }}
            image={require("../assets/Icon_Gold_48x48.png")}
          >
            <Callout
              style={styles.callout}
              onPress={() =>
                navigation.navigate("GigDetails", {
                  venue: gig.venue,
                  date: gig.date,
                  gigName: gig.gigName,
                  time: gig.time,
                })
              }
            >
              <CalloutView
                venue={gig.venue}
                date={gig.date}
                gigName={gig.gigName}
                time={gig.time}
                style={styles.calloutView}
              />
            </Callout>
          </Marker>
        ))}
      </MapView>
      <View style={styles.buttonOptions}>
        <Pressable onPress={() => adjustDate(-1)}>
          <Text style={styles.buttonOptionsText}>前日演出</Text>
        </Pressable>
        <Pressable onPress={() => adjustDate(1)}>
          <Text style={styles.buttonOptionsText}>次日演出</Text>
        </Pressable>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "column",
    alignItems: "center",
  },
  map: {
    height: 500,
    width: 330,
    margin: 10,
  },
  headerText: {
    color: "black",
    fontSize: 20,
    marginTop: 5,
  },
  callout: {
    width: 200,
    height: 100,
  },
  buttonOptions: {
    flexDirection: "row",
    justifyContent: "flex-start",
  },
  buttonOptionsText: {
    margin: 5,
  },
});

export default GigMap;

关键优化点

  1. 统一日期状态:用currentDate状态存储Date对象,所有日期操作都基于这个状态,避免变量和状态混用。
  2. 不可变日期操作:每次修改日期时都创建新的Date对象,不直接修改原对象,确保React能检测到状态变化触发重渲染。
  3. 工具函数封装:把日期格式化、小时偏移的逻辑封装成工具函数,提高代码复用性和可读性。
  4. 初始化逻辑修正:在useEffect中初始化日期,避免组件初始化时变量未定义的问题。
  5. 统一筛选逻辑:基于currentDate状态生成格式化字符串,用于筛选演出,确保匹配Firestore的日期格式。
  6. 优化key值:优先使用演出文档的ID作为Marker的key,避免使用索引key带来的渲染问题。

内容的提问来源于stack exchange,提问作者Josh Simon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 10:25:20