I am using react-day-picker (http://react-day-picker.js.org/) and im having troubles with performance.
I want to display a huge amount of calendars which are then hidden and scrolled by scrollbar. However, when amount of calendars is too big, selecting days works too slow (for circa 10 years displayed it takes about 1second, sometimes even more). I figured out, that probably the reason why this happens is that after selecting days in calendar each day rerenders (and for 10*365 days it does make a difference). How can i prevent days from rerendering? I know that i should probably use useMemo hook, but im not exactly sure how
BigCalendar code:
import React, { useState, useEffect, useMemo } from 'react';
import DayPicker, { DateUtils } from 'react-day-picker';
import '../styles/Calendar.scss';
import 'react-day-picker/lib/style.css';
import getStyles from '../utils/CalendarSizes';
import Weekday from './calendar-elements/Weekday';
import Caption from './calendar-elements/Caption';
import Day from './calendar-elements/Day';
import { compareDates } from '../utils/DateUtils';
/**
* Funkcja dodaj?ca dni spe?niaj?ce warunek do danego modyfikatora stylu
* @param {object} modifier poprzedni modyfikator
* @param {Array} data tablica z danymi dotycz?cych dni
*/
const addDataToModifiers = (modifier, data) => {
data.forEach(data => modifier[data.Name] = /*{from: data.from, to: data.to}*/ data.Days)
return modifier;
}
/**
* Funkcja dodaj?ca styl do modifikatora
* @param {object} modifier poprzednie style modyfikatora
* @param {Array} data tablica danymi dotycz?ca kolorów komórek typu dnia
*/
const addStylesToDayTypesModifiers = (modifier, data) => {
data.forEach(data => modifier[data.Name] = { backgroundColor: data.Color });
return modifier;
}
/**
* Funkcja zwracaj?ca ilo?c miesi?cy pomi?dzy dwiema datami
* @param {Date} from pierwsza data
* @param {Date} to druga data
*/
const calculateNumberOfMonths = (from, to) => {
let fromMonthValue = from.getMonth();
let toMonthValue = to.getMonth();
let fromYearValue = from.getYear();
let toYearValue = to.getYear();
return (toYearValue - fromYearValue) * 12 + (toMonthValue - fromMonthValue) + 1;
}
/**
* Funkcja dodaj?ca styl do modifikatora
* @param {object} modifier poprzednie style modyfikatora
* @param {Array} data tablica danymi dotycz?ca kolorów ramek komórek typu rozk?adu
*/
const addStylesToScheduleTypesModifiers = (modifier, data) => {
data.forEach(data => modifier[data.Name] = { borderColor: data.Color });
return modifier;
}
/**
* Funkcja zwracaj?ca tablice dat w podanym przedziale (obustronnie domkni?ta)
* @param {Date} from pocz?tek przedzia?u
* @param {Date} to koniec przedzia?u
*/
const getDaysBetweenDates = (start, end) => {
if (!start || !end) {
return [];
}
if (start === end) {
return [start];
}
for (var arr = [], dt = new Date(start); dt <= end; dt.setDate(dt.getDate() + 1)) {
arr.push(new Date(dt));
}
// return arr.map(date => ({ Date: date }));
return arr;
};
const getDisabledDays = (start, end) => {
let disabledDays = [];
for (let i = 1; i < start.getDate(); i++) {
disabledDays.push(new Date(start.getFullYear(), start.getMonth(), i));
}
let endDate = new Date(end.getFullYear(), end.getMonth(), 0).getDate();
for (let i = end.getDate() + 1; i <= endDate; i++) {
disabledDays.push(new Date(end.getFullYear(), end.getMonth(), i));
}
return disabledDays;
}
const getModifiers = (dayTypesData, scheduleTypesData) => {
let modifiers = {
all: { daysOfWeek: [0, 1, 2, 3, 4, 5, 6] }
}
modifiers = addDataToModifiers(modifiers, dayTypesData);
modifiers = addDataToModifiers(modifiers, scheduleTypesData);
return modifiers;
}
const BigCalendar = (props) => {
const { fromMonth, toMonth, dayTypesData, scheduleTypesData, updatedTypeColor, selectedDaysCallback, orgUnitId, ...rest } = props;
const [selectedDays, setSelectedDays] = useState([]);
const [firstClickedDay, setFirstClickedDay] = useState(null);
let modifiers = useMemo(() => getModifiers(dayTypesData, scheduleTypesData), [dayTypesData, scheduleTypesData]);
const disabledDays = getDisabledDays(fromMonth, toMonth);
useEffect(() => {
setSelectedDays([]);
setFirstClickedDay(null);
selectedDaysCallback([]);
}, [orgUnitId])
/**
* Funkcja zaznaczaj?ca dni w kalendarzu
* @param {any} day klikni?ta data
* @param {any} e obiekt zdarzenia
*/
const handleDaySelected = (day, { }, e) => {
let newValue;
if (disabledDays.some(disabledDay => compareDates(disabledDay, day))) {
return;
}
//zwyk?e klikni?cie: zaznaczamy dzień i usuwamy reszt? zaznaczenia
if ((!(e.shiftKey || e.ctrlKey))) {
newValue = {
from: day,
to: day
};
newValue = [...getDaysBetweenDates(newValue.from, newValue.to)];
}
//klikni?cie z shiftem: zaznaczamy przedzia?
else if (e.shiftKey) {
let newFirstClickedDay = null;
//je?li pierwszy dzień przedzia?u nie zosta? wybrany, to go ustawiamy
if (!firstClickedDay) {
if (selectedDays.find(selDay => compareDates(selDay, day))){
newValue = {
from: null,
to: null
}
}
else {
newValue = {
from: day,
to: day,
};
newFirstClickedDay = day;
}
}
//je?li zosta?, to obecnie wybrany dzień jest końcem lub pocz?tkiem przedzia?u
else {
let [from, to] = [null, null];
if (firstClickedDay.getTime() <= day.getTime()) {
let beginDate = new Date(firstClickedDay);
beginDate.setDate(firstClickedDay.getDate() + 1);
[from, to] = [beginDate, day]
}
else {
let endDate = new Date(firstClickedDay);
endDate.setDate(firstClickedDay.getDate() - 1);
[from, to] = [day, endDate];
}
newValue = {
from: from,
to: to,
};
}
newValue = [...selectedDays, ...getDaysBetweenDates(newValue.from, newValue.to)];
setFirstClickedDay(newFirstClickedDay);
}
// klikni?cie z ctrlem: dodanie/usuni?cie pojedynczego dnia
else if (e.ctrlKey) {
let selectedDay = selectedDays.find(selDay => compareDates(selDay, day));
//je?eli dzień nie by? zaznaczony, to go dodajemy
if (!selectedDay) {
newValue = [...selectedDays, day];
}
//w przeciwynym wypadku usuwamy, je?li by? klikni?ty z shiftem, to usuwamy te? z tej pozycji
else {
newValue = selectedDays.filter(selDay => !compareDates(selDay, day));
if (newValue.length === 0 || (firstClickedDay && compareDates(day, firstClickedDay))) {
setFirstClickedDay(null);
}
}
}
newValue = [...new Set(newValue.sort((a, b) => a.getTime() - b.getTime()))];
//console.log(newValue);
setSelectedDays(newValue);
selectedDaysCallback(newValue);
}
//funkcja zwracaj?ca pocz?tkow? warto?? modyfikatorów
const getInitialModifiers = () => {
let modifiersStyles = {
all: getStyles(props.numberOfMonths).modifiersStyle.all,
}
modifiersStyles = addStylesToDayTypesModifiers(modifiersStyles, props.dayTypesData);
modifiersStyles = addStylesToScheduleTypesModifiers(modifiersStyles, props.scheduleTypesData);
return modifiersStyles;
}
const [modifiersStyles, setModifiersStyles] = useState(getInitialModifiers())
useEffect(() => {
setModifiersStyles(getInitialModifiers());
}, [updatedTypeColor, dayTypesData, scheduleTypesData])
let numberOfMonths = calculateNumberOfMonths(fromMonth, toMonth);
return (
<div>
<div {...rest} className={'calendar-flex-container'}>
<DayPicker
modifiers={modifiers}
modifiersStyles={modifiersStyles}
className="calendar-flex-item"
month={fromMonth}
numberOfMonths={numberOfMonths}
onDayClick={handleDaySelected}
selectedDays={selectedDays}
canChangeMonth={false}
weekdayElement={<Weekday numberOfMonths={numberOfMonths} />}
captionElement={<Caption numberOfMonths={numberOfMonths} />}
renderDay={(day, currentModifiers) =>
<Day day={day}
currentModifiers={currentModifiers}
modifiersStyles={modifiersStyles}
allModifiers={modifiers} />}
disabledDays={disabledDays}
/>
</div>
</div>
)
}
export default BigCalendar;
Day code:
import React from 'react';
import '../../styles/Day.scss';
import { compareDates } from '../../utils/DateUtils';
/**
* Funkcja zwracaj?ca styl wewn?trnej cz??ci komórki dnia
* @param {object} currentModifiers modyfikatory do których nale?y obecny dzień
* @param {object} modifiersStyles style modyfikatorow
*/
const getInnerCombinedStyle = (currentModifiers, modifiersStyles) => {
let modifiersArray = Object.entries(currentModifiers);
let modifiersStylesMap = new Map(Object.entries(modifiersStyles));
let resultingStyle = modifiersArray
.filter(([key, value]) => value)
.map(([key, value]) => modifiersStylesMap.get(key))
.reduce((a, b) => ({ ...a, ...b }), {})
return resultingStyle;
}
/**
* Funkcja tworz?ca ramk? wokó? komórki dnia
* @param {Date} day dzień któremu tworzona jest ramka
* @param {object} modifier modyfikator odpowiadaj?cy za ramk?
* @param {object} allModifiers wszystkie modyfikatory
* @param {object} modifiersStyleMap mapa styli modyfikatorów
*/
const createBorder = (day, modifier, allModifiers, modifiersStylesMap) => {
let [key, value] = modifier;
let style = modifiersStylesMap.get(key);
let currentModifier = allModifiers[key];
//console.log("day render");
if (!style) {
return style;
}
if (currentModifier && currentModifier.from && currentModifier.to) {
//je?eli jest to pierwszy dzień przedzia?u, to ustawiamy górn?, doln? oraz lew? ramk?
if (compareDates(day, currentModifier.from)) {
let borderBottom = { borderBottomColor: style.borderColor};
return { borderLeftColor: style.borderColor, borderTopColor: style.borderColor, ...borderBottom, borderRight: 0 };
}
// je?li jest to ostatni dzień, to ustawiamy górn?, doln? i praw? ramk?
else if (compareDates(day, currentModifier.to)) {