I am currently building a scheduling app. If a user selects two dates, I am attempting to select all date blocks between the two selected dates in the calendar as well. I am able to achieve this, but it causes my useEffect to fire into an infinite loop because I have state as a dependency in my useEffect where I am setting state. I am unsure of the best method to prevent the infinite loop behavior. The useEffect in question is the bottom one. My code is as follows:
export default function App() {
const [selectedDate, handleDateChange] = useState(
dayjs().format("YYYY-MM-DD")
);
const [events] = useState([
{
id: "5e24d1fa-aa66-4122-b1eb-97792f0893b0",
name: "Rodriquez Family",
selectedDates: ["2021-05-01"],
status: "submitted"
},
{
id: "269a0381-63c7-4ab6-92d8-7f7b836aee6f",
name: "Test Family",
selectedDates: ["2021-05-03"],
status: "submitted"
}
]);
const [data, setData] = useState([]);
const getDaysArray = async (firstDay, lastDay) => {
let dates = [];
var dow = dayjs(firstDay).day();
while (dow > 0) {
dates.push(null);
dow = dow - 1;
}
while (firstDay <= lastDay) {
dates.push(firstDay);
firstDay = dayjs(firstDay).add(1, "days").format("YYYY-MM-DD");
}
return dates;
};
useEffect(() => {
const getDates = async () => {
const firstDay = dayjs(selectedDate)
.startOf("month")
.format("YYYY-MM-DD");
const lastDay = dayjs(firstDay).endOf("month").format("YYYY-MM-DD");
const dates = await getDaysArray(firstDay, lastDay);
const list = dates.map((date) => {
const event = events.find(({ selectedDates = [] }) =>
selectedDates.includes(date)
);
return event ? { date, event } : { date, event: null, checked: false };
});
setData(list);
};
getDates();
}, [events, selectedDate]);
const selectDate = (date) => {
setData(
(a) =>
a &&
a.map((item) =>
item.date === date ? { ...item, checked: !item.checked } : item
)
);
};
useEffect(() => {
if (data && data.filter((res) => res.checked).length > 1) {
const filterDates = data.filter((r) => r.checked);
const startDate = filterDates[0].date;
const endDate = filterDates[filterDates.length - 1].date;
const datesToUpdate = data.filter(
(res) => res.date > startDate && res.date < endDate
);
const newArr = data.map((date) => {
const updateCheck = datesToUpdate.find((r) => r.date === date.date);
return updateCheck ? { ...updateCheck, checked: true } : date;
});
setData(newArr);
}
}, [data]);
return (
<MuiPickersUtilsProvider utils={DayJsUtils}>
<div className="App">
<DatePicker
minDate={dayjs()}
variant="inline"
openTo="year"
views={["year", "month"]}
label="Year and Month"
helperText="Start from year selection"
value={selectedDate}
onChange={handleDateChange}
/>
</div>
<div className="cal">
<div className="cal-div1"></div>
<div className="cal-div2 "></div>
<div className="cal-div3 cal-cir-hov"></div>
<div className="cal-div4"> SUN </div>
<div className="cal-div5"> MON </div>
<div className="cal-div6"> TUE </div>
<div className="cal-div7"> WED </div>
<div className="cal-div8"> THU </div>
<div className="cal-div9"> FRI </div>
<div className="cal-div10"> SAT </div>
{data &&
data.map((r, i) => {
return (
<>
<div
onClick={() =>
!r.checked &&
r.date >= dayjs().format("YYYY-MM-DD") &&
!r.event &&
selectDate(r.date)
}
style={
r.checked
? { backgroundColor: "green" }
: { color: "#565254" }
}
key={i}
className="cal-cir-hov"
>
<div>{r.date} </div>
<div
style={
r.event?.status === "submitted"
? { color: "orange" }
: { color: "green" }
}
>
{r.event?.name}
</div>
</div>
</>
);
})}
</div>
</MuiPickersUtilsProvider>
);
}
attached is a code sandbox for debugging and to show the behavior I am currently talking about. Select two separate dates that are greater than today and you will see all the dates in between are selected, but the app goes into a loop https://codesandbox.io/s/dawn-snow-03r59?file=/src/App.js:301-4499
useEffectfor whatever functionality you've written up. Again: it's a long page, but read it anyway. It is the most authoritative source, and well written, documenting exactly whatuseEffectis for.