I’m creating a calendar date range picker in react js and I’m running into an issue where, whenever I click a day in the calendar, all of the days end up re-rendering.
Here is my main calendar component
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import CalendarDay from './CalendarDay';
export default function Calendar(props) {
const days = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
];
const [selectedDates, setSelectedDates] = useState({
start: null,
end: null,
});
useEffect(() => {
console.log(selectedDates);
}, [selectedDates]);
const handleDateSelect = useCallback(
(day) => {
if (!selectedDates.start) {
setSelectedDates((prev) => ({ ...prev, start: day }));
} else if (!selectedDates.end) {
setSelectedDates((prev) => ({ ...prev, end: day }));
} else {
setSelectedDates({ start: day, end: null });
}
},
[selectedDates.start, selectedDates.end]
);
return (
<>
<button onClick={() => setSelectedDates({ start: null, end: null })}>
clear dates
</button>
<div>start: {selectedDates.start}</div>
<div>end: {selectedDates.end}</div>
<div className="calendar">
{days.map((day, idx) => {
const key = `${props.year}-${props.month}-${idx + 1}`;
const isSelectedOrInRange = () => {
if (
selectedDates.start === day ||
selectedDates.end === day ||
(day <= selectedDates.end && day >= selectedDates.start)
) {
return true;
}
return false;
};
return (
<CalendarDay
key={key}
day={day}
isSelectedOrInRange={isSelectedOrInRange()}
handleDateSelect={handleDateSelect}
/>
);
})}
</div>
</>
);
}
And here is my CalendarDay component (this is whats always re-rendering)
import React, { memo } from 'react';
function CalendarDay(props) {
const { handleDateSelect, day, isSelectedOrInRange } = props;
console.log(' - rendering: ', day);
return (
<div
className={'day' + (isSelectedOrInRange ? ' selected' : '')}
onClick={() => handleDateSelect(day)}
>
{day}
</div>
);
}
export default memo(CalendarDay);
styles.css (imported in index.js)
* {
box-sizing: border-box;
}
.calendar {
width: 400px;
display: flex;
flex-wrap: wrap;
}
.day {
width: calc(400px / 7);
height: 50px;
border: 1px solid green;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.selected {
color: white;
background: red;
}
Is there a way to prevent any calendarDay box from re-rendering unless its clicked on or a date in between the selection?
Here is a stackblitz for you to play around with
>Solution :
Since you’re using React.memo, the CalendarDay only renders when its props change, but you’re changing the handleDateSelect function anytime selectedDates.start or selectedDate.end changes.
One way to avoid this is to only use the prev value in the handleDateSelect function, avoiding the need to declare any dependencies for the callback:
const handleDateSelect = useCallback(
(day) => {
setSelectedDates((prev) =>
!prev.start
? { ...prev, start: day }
: !prev.end
? { ...prev, end: day }
: { start: day, end: null }
},
[]
);