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

React Big Calendar中onRangeChange无法更新backgroundEvents问题

解决React Big Calendar中onRangeChange更新backgroundEvents时日历无法切换的问题

问题原因

当在onRangeChange钩子中更新组件state(如backgroundEvents)时,组件会重新渲染。由于你使用的是非受控模式(仅依赖defaultView),日历会重置回初始的当前周范围,导致无法切换到新的周/日视图。

解决方案

将日历改为受控模式,手动维护当前显示的日期,并修正日期循环中的潜在问题:

1. 添加受控状态

新增currentDate state来控制日历的当前显示日期:

const [currentDate, setCurrentDate] = useState(new Date());

2. 修改Calendar组件属性

给DnDCalendar添加date属性绑定currentDate:

<DnDCalendar
  // 原有属性保留...
  date={currentDate} // 新增受控日期属性
/>

3. 更新handleRangeChange逻辑

在handleRangeChange中,不仅更新backgroundEvents,还要同步更新currentDate为新范围的起始日期:

const handleRangeChange = (range) => {
  const dateRange = Array.isArray(range) ? range : [range.start, range.end];
  console.log("Date Range:", dateRange);
  
  // 更新当前显示日期为新范围的起始
  setCurrentDate(new Date(dateRange[0]));
  
  // 生成并更新背景事件
  const newBackgroundEvents = getBackgroundEvents(dateRange);
  setBackgroundEvents(newBackgroundEvents);
};

4. 修正getBackgroundEvents中的日期循环问题

原代码中直接修改循环变量date会导致日期比较异常,需每次创建新的Date对象:

const getBackgroundEvents = (range) => {
  let events = [];
  // 复制起始日期,避免修改原对象
  let currentDate = new Date(range[0]);
  
  while (currentDate <= range[1]) {
    // Adjusting so that Monday = 0, Tuesday = 1, ..., Sunday = 6
    const day = (currentDate.getDay() + 6) % 7;
    const formattedDate = formatISO(currentDate, { representation: "date" });
    const specialHours = specialOpeningHours.filter((special) => 
      special.open.startsWith(formattedDate)
    );
    
    let hours = specialHours.length > 0 ? specialHours : openingHours[day];
    
    hours.forEach((slot) => {
      let start, end;
      if (specialHours.length > 0) {
        start = parseISO(slot.open);
        end = parseISO(slot.close);
      } else {
        start = parseISO(`${formattedDate}T${slot.open}`);
        end = parseISO(`${formattedDate}T${slot.close}`);
      }
      events.push({ start, end });
    });
    
    // 创建新的日期对象,避免修改循环变量
    currentDate = new Date(currentDate);
    currentDate.setDate(currentDate.getDate() + 1);
  }
  return events;
};

修改后的完整代码

import "../../../styles/components/scheduler.scss";
import React, { useEffect, useState, useCallback, useRef } from "react";
import { getISOWeek, addYears, subYears, parseISO, isSameDay, formatISO } from "date-fns";
import { Calendar, dateFnsLocalizer, Views, CalendarProps } from "react-big-calendar";
import format from "date-fns/format";
import parse from "date-fns/parse";
import startOfWeek from "date-fns/startOfWeek";
import endOfWeek from "date-fns/endOfWeek";
import getDay from "date-fns/getDay";
import de from "date-fns/locale/de";
import withDragAndDrop from "react-big-calendar/lib/addons/dragAndDrop/withDragAndDrop";

const openingHours = {
  0: [ { open: "04:00", close: "12:00" }, { open: "13:00", close: "17:45" } ],
  1: [{ open: "05:00", close: "17:45" }],
  2: [{ open: "06:00", close: "17:45" }],
  3: [{ open: "08:00", close: "17:45" }],
  4: [ { open: "06:30", close: "12:00" }, { open: "13:30", close: "23:00" } ],
  5: [{ open: "08:00", close: "17:45" }],
  6: [],
};

const specialOpeningHours = [
  { open: "2024-01-05T05:00:00", close: "2024-01-05T12:00:00" },
  { open: "2024-01-05T13:00:00", close: "2024-01-05T23:59:00" },
];

const myEventsList = [
  { title: "My Event", start: parseISO("2024-01-07T13:05:00"), end: parseISO("2024-01-07T14:00:00") },
  { title: "My Event 2", start: parseISO("2024-01-07T13:35:00"), end: parseISO("2024-01-07T14:30:00") },
  { title: "My Event 3", start: parseISO("2024-01-12T13:35:00"), end: parseISO("2024-01-12T14:30:00") },
];

function Scheduler() {
  const [backgroundEvents, setBackgroundEvents] = useState([]);
  const [currentDate, setCurrentDate] = useState(new Date()); // 新增受控日期状态
  const clickRef = useRef(null);

  const getCurrentWeekRange = () => {
    const today = new Date();
    const start = startOfWeek(today, { weekStartsOn: 0 });
    const end = endOfWeek(today, { weekStartsOn: 0 });
    return [start, end];
  };

  useEffect(() => {
    const currentWeekRange = getCurrentWeekRange();
    handleRangeChange(currentWeekRange);
  }, []);

  useEffect(() => {
    return () => {
      window.clearTimeout(clickRef?.current);
    };
  }, []);

  const DnDCalendar = withDragAndDrop(Calendar);

  const locales = { de: de };

  const messages = {
    week: "Woche",
    work_week: "Arbeitswoche",
    day: "Tag",
    month: "Monat",
    previous: "zurück",
    next: "vor",
    today: "Heute",
    agenda: "Liste",
    showMore: (total) => `+${total} más`,
  };

  const localizer = dateFnsLocalizer({ format, parse, startOfWeek, getDay, locales });

  function updateEvent(event) {
    console.log(event);
    console.log(event.start.toUTCString());
  }

  function newEvent(event) {
    console.log(event);
  }

  const onSelectEvent = useCallback((calEvent) => {
    console.log("onSelectEvent");
    window.clearTimeout(clickRef?.current);
    clickRef.current = window.setTimeout(() => {
      console.log(calEvent);
    }, 250);
  }, []);

  const onDoubleClickEvent = useCallback((calEvent) => {
    console.log("onDoubleClick");
    window.clearTimeout(clickRef?.current);
    clickRef.current = window.setTimeout(() => {
      console.log(calEvent);
    }, 250);
  }, []);

  const handleRangeChange = (range) => {
    const dateRange = Array.isArray(range) ? range : [range.start, range.end];
    console.log("Date Range:", dateRange);
    
    // 更新当前显示日期
    setCurrentDate(new Date(dateRange[0]));
    
    const newBackgroundEvents = getBackgroundEvents(dateRange);
    setBackgroundEvents(newBackgroundEvents);
  };

  const getBackgroundEvents = (range) => {
    let events = [];
    let currentDate = new Date(range[0]);
    
    while (currentDate <= range[1]) {
      const day = (currentDate.getDay() + 6) % 7;
      const formattedDate = formatISO(currentDate, { representation: "date" });
      const specialHours = specialOpeningHours.filter((special) => 
        special.open.startsWith(formattedDate)
      );
      
      let hours = specialHours.length > 0 ? specialHours : openingHours[day];
      
      hours.forEach((slot) => {
        let start, end;
        if (specialHours.length > 0) {
          start = parseISO(slot.open);
          end = parseISO(slot.close);
        } else {
          start = parseISO(`${formattedDate}T${slot.open}`);
          end = parseISO(`${formattedDate}T${slot.close}`);
        }
        events.push({ start, end });
      });
      
      currentDate = new Date(currentDate);
      currentDate.setDate(currentDate.getDate() + 1);
    }
    return events;
  };

  return (
    <div className="w-full h-full">
      <DnDCalendar
        defaultView="week"
        selectable="true"
        localizer={localizer}
        culture="de"
        events={myEventsList}
        startAccessor="start"
        endAccessor="end"
        style={{ height: "100%" }}
        messages={messages}
        views={[Views.WEEK, Views.DAY]}
        dayLayoutAlgorithm="no-overlap"
        onEventDrop={updateEvent}
        onEventResize={updateEvent}
        onSelectEvent={onSelectEvent}
        onSelectSlot={newEvent}
        backgroundEvents={backgroundEvents}
        onDoubleClick={onDoubleClickEvent}
        onRangeChange={handleRangeChange}
        date={currentDate} // 绑定受控日期
      />
    </div>
  );
}

export default Scheduler;

关键说明

  • 受控模式:通过date属性手动控制日历显示的日期,避免重新渲染时重置范围
  • 日期循环:每次创建新的Date对象,防止修改循环变量导致的日期比较错误
  • 特殊时间覆盖逻辑:保持原有的特殊营业时间优先于常规时间的逻辑不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 15:17:05