Why React not re-rendering components even if it’s state changes after an API call

I’ve a React state like this initialized with an empty array when component initialises

const [upcommingDates, setUpcommingDates] = useState([]);

And this calendar component depends on this state. (This is a chld component of our main component and has this state dependency)

<Calender name="Eat Eggs" dates={upcommingDates} />

Once component loads, I call an API and fills the array once API promise resolves

async function refreshPattern()
{
    //Call the API
    let pattern = await GetRepeatPattern(id);
    //Update State
    setUpcommingDates(pattern.UpcommingDates);

    setNextOccurance(pattern.NextOccurance);       
}

This is my useEffect

useEffect(async () => {
 refreshPattern();
},[]);

Now the issue is

  • The callender component is rendering only once with that empty array in it’s props
  • React is not re-rendering even after I change state
  • When I put ‘upcommingDates’ inside useEffect(…,[upcommingDates]) second parameter, React falls to an infinite loop

This is the API response from console .logging the ‘pattern’ variable enter image description here

How to make the Calendar component re-render? And why react is not re-rendering component even if I changed my state?

NOTE: This is the full component.

export const RepeatSelector = ({ id }) => {
let dropdownPatternMode = null;
const [every, setEvery] = useState(1);
const [timeMode, setTimeMode] = useState(1);
const [course, setCourse] = useState(1);
const [constrain, setConstrain] = useState(1);
const [patternMode, setPatternMode] = useState(1);
const [nextOccurance, setNextOccurance] = useState('Loading...');
const [upCommingDates, setUpcommingDates] = useState([]);
const [dayOfMonth, setDayOfMonth] = useState(1);
const [logicalStart, setLogicalStart] = useState(1);
const [logicalDay, setLogicalDay] = useState(1);
const [pattern, setPattern] = useState({
Value: '',
Mode: '',
Constrain: '',
Time: '',
Date: ''
});
const [startDay, setStartDay] = useState(new Date());
const [startTime, setStartTime] = useState('00:00');
const [days, setDays] = useState([
{
selected: false,
name: "SUN"
},
{
selected: false,
name: "MON"
},
{
selected: true,
name: "TUE"
},
{
selected: true,
name: "WED"
},
{
selected: false,
name: "THU"
},
{
selected: false,
name: "FRI"
},
{
selected: false,
name: "SAT"
},
]);
function init() {
dropdownPatternMode = new Choices(document.getElementById('everyCount'), { searchEnabled: false, shouldSort: false });
}
useEffect(async function () {
init();
let response = await GetActivityRepeat(id);
setDayOfMonth(response.DayOfMonth);
days.map((day, index) => {
if (day.name == 'SUN') {
days[index] = { selected: response.Sunday, name: 'SUN' };
}
else if (day.name == 'MON') {
days[index] = { selected: response.Monday, name: 'MON' };
}
else if (day.name == 'TUE') {
days[index] = { selected: response.Tuesday, name: 'TUE' };
}
else if (day.name == 'WED') {
days[index] = { selected: response.Wednesday, name: 'WED' };
}
else if (day.name == 'THU') {
days[index] = { selected: response.Thursday, name: 'THU' };
}
else if (day.name == 'FRI') {
days[index] = { selected: response.Friday, name: 'FRI' };
}
else if (day.name == 'SAT') {
days[index] = { selected: response.Saturday, name: 'SAT' };
}
return days;
});
setDays(days);
setLogicalStart(response.LogicalStart);
setLogicalDay(response.LogicalDay);
setCourse(response.Course);
setEvery(response.PatternValue);
setPatternMode(response.PatternMode);
dropdownPatternMode.setChoiceByValue([response.PatternMode.toString()]);
setStartDay(new Date(response.StartBy));
setConstrain(response.Constrain);
let myTime = response.SpecificTime.split('T')[1];
setStartTime(myTime);
async function refreshPattern()
{
//Call the API
let pattern = await GetRepeatPattern(id);
//Update State
setUpcommingDates(pattern.UpcommingDates);
setPattern(pattern);
setNextOccurance(pattern.NextOccurance);       
}
refreshPattern();
setTimeMode(response.TimeMode);
}, []);
async function SaveData() {
let sDate = new Date().toISOString().substring(0, 10) + " 00:00";
let sTime = new Date().toISOString().substring(0, 10) + " 00:00";
try {
sDate = startDay.toISOString().substring(0, 10) + " 00:00";
}
catch
{
store.addNotification({
title: "Start Date Required",
message: "Please choose a start date for this activity",
type: "warning",
insert: "top",
container: "top-right",
animationIn: ["animate__animated", "animate__fadeIn"],
animationOut: ["animate__animated", "animate__fadeOut"],
dismiss: {
duration: 2000,
pauseOnHover: true
}
});
return;
}
try {
sTime = new Date().toISOString().substring(0, 10) + " " + startTime;
}
catch
{
if (timeMode == 2) {
store.addNotification({
title: "Start Time Required",
message: "Please choose a start time for this activity",
type: "warning",
insert: "top",
container: "top-right",
animationIn: ["animate__animated", "animate__fadeIn"],
animationOut: ["animate__animated", "animate__fadeOut"],
dismiss: {
duration: 2000,
pauseOnHover: true
}
});
return;
}
}
var data = {
ActivityId: parseInt(id),
Course: course,
PatternValue: every,
PatternMode: patternMode,
Sunday: days.find((e) => e.name == 'SUN').selected,
Monday: days.find((e) => e.name == 'MON').selected,
Tuesday: days.find((e) => e.name == 'TUE').selected,
Wednesday: days.find((e) => e.name == 'WED').selected,
Thursday: days.find((e) => e.name == 'THU').selected,
Friday: days.find((e) => e.name == 'FRI').selected,
Saturday: days.find((e) => e.name == 'SAT').selected,
Constrain: constrain,
DayOfMonth: dayOfMonth,
LogicalStart: logicalStart,
LogicalDay: logicalDay,
StartBy: sDate,
TimeMode: timeMode,
SpecificTime: sTime,
};
await SaveActivityRepeat(data);
store.addNotification({
title: "Activity Saved",
message: "Data saved successfully. Changes will reflect soon",
type: "success",
insert: "top",
container: "top-right",
animationIn: ["animate__animated", "animate__fadeIn"],
animationOut: ["animate__animated", "animate__fadeOut"],
dismiss: {
duration: 2000,
pauseOnHover: true
}
});
//refreshPattern();
}
const renderRepeatPattern = () => {
return (
<>
<ul className="list-group mt-3">
<li className="list-group-item border-0 d-flex p-4 mb-2 bg-gray-100 border-radius-lg">
<div className="d-flex flex-column">
<h6 className="mb-3 text-sm">Current Configuration</h6>
<span className="mb-2 text-xs">Pattern:
<span className="text-danger font-weight-bold ms-sm-2">{pattern.Value} {pattern.Mode} {pattern.Constrain} {pattern.Time} {pattern.Date}</span>
</span>
<span className="mb-2 text-xs">Next Occurance:
<span className="text-dark ms-sm-2 font-weight-bold">{nextOccurance}</span></span>
</div>
<div className="ms-auto text-end">
<a className="btn btn-link text-primary px-3 mb-0" href="javascript:;"><i className="fas fa-pencil-alt text-dark me-2" aria-hidden="true" />View Calender</a>
</div>
</li>
</ul>
<Calender name="Eat Eggs" dates={upCommingDates} />
</>
);
}
const renderDaySelector = () => {
if (patternMode == 1) {
return (
<>
{renderRepeatPattern()}
<hr className="horizontal dark mt-3" />
<label>Start From</label>
<div className="form-check">
<div className="row">
<div className="col-md-12">
<DatePicker value={startDay} onChange={setStartDay} />
</div>
</div>
</div>
</>
);
}
else if (patternMode == 2) {
return (
<>
<div className="col-md-12 mt-3">
<DaySelector data={days} onDataUpdate={(e) => setDays(e)} />
</div>
<>
{renderRepeatPattern()}
<hr className="horizontal dark mt-3" />
<label>Start From</label>
<div className="form-check">
<div className="row">
<div className="col-md-12">
<DatePicker value={startDay} onChange={setStartDay} />
</div>
</div>
</div>
</>
</>
);
}
else if (patternMode == 3) {
return (
<div className="col-12 col-sm-12">
<label>Constrain By</label>
<div className="form-check">
<input checked={constrain === 1} className="form-check-input" type="radio" name="constrainBy" id="radDayOfMonth" onChange={() => setConstrain(1)} />
<label className="custom-control-label" htmlFor="radDayOfMonth">Day of Month</label>
<div className="row">
<div className="col-md-4">
<DayDropdown onSelection={(e) => setDayOfMonth(e)} value={dayOfMonth} />
</div>
</div>
</div>
<div className="form-check">
<input checked={constrain === 2} className="form-check-input" type="radio" name="constrainBy" id="radLogicaly" onChange={() => setConstrain(2)} />
<label className="custom-control-label" htmlFor="radLogicaly">Logicaly</label>
<div className="row">
<div className="col-md-4">
<WeekDropdown onSelection={(e) => setLogicalStart(e)} value={logicalStart} />
</div>
<div className="col-md-4">
<DayNameDropdown onSelection={(e) => setLogicalDay(e)} value={logicalDay} />
</div>
</div>
</div>
{renderRepeatPattern()}
<hr className="horizontal dark mt-3" />
<label>Start From</label>
<div className="form-check">
<div className="row">
<div className="col-md-12">
<DatePicker value={startDay} onChange={setStartDay} />
</div>
</div>
</div>
</div>
);
}
else if (patternMode == 4) {
return (
<>
{renderRepeatPattern()}
<hr className="horizontal dark mt-3" />
<label>Start From</label>
<div className="form-check">
<div className="row">
<div className="col-md-12">
<DatePicker value={startDay} onChange={setStartDay} />
</div>
</div>
</div>
</>
);
}
}
return (
<>
<div className="row">
{/*COURSE*/ }
<div className="col-12 col-sm-12">
<label>Course</label>
<div className="col-12 col-sm-12">
<div className="form-check">
<input checked={course === 1} className="form-check-input" type="radio" name="course" id="radOneTime" onChange={() => setCourse(1)} />
<label className="custom-control-label" htmlFor="radOneTime">One Time</label>
</div>
<div className="form-check">
<input checked={course === 2} className="form-check-input" type="radio" name="course" id="radMultipleTimes" onChange={() => setCourse(2)} />
<label className="custom-control-label" htmlFor="radMultipleTimes">Multiple Times</label>
</div>
</div>
</div>
{/*PATTERN*/}
<label className="custom-control-label mt-3" htmlFor="customRadio1">Running Pattern</label>
<div className="col-md-1">
<label className="form-check-label" htmlFor="flexSwitchCheckDefault">Every</label>
</div>
<div className="col-md-2">
<input className="form-control" type="number" value={every} onChange={e => setEvery(Number(e.target.value))} />
</div>
<div className="col-md-4">
<select className="form-control" id="everyCount" onChange={e=>setPatternMode(Number(e.target.value))}>
<option value="1">Day</option>
<option value="2">Week</option>
<option value="3">Month</option>
<option value="4">Year</option>
</select>
</div>
{renderDaySelector()}
{/*TIME*/}
<label className="mt-3">Set Time</label>
<div className="form-check">
<div className="row">
<div className="col-12 col-sm-12">
<div className="form-check">
<input checked={timeMode === 1} className="form-check-input" type="radio" name="time" id="radAdapt" onChange={() => setTimeMode(1)} />
<label className="custom-control-label" htmlFor="radAdapt">Adapt Automaticaly</label>
</div>
<div className="form-check">
<input checked={timeMode === 2} className="form-check-input" type="radio" name="time" id="radSpecificDate" onChange={() => setTimeMode(2)} />
<label className="custom-control-label" htmlFor="radSpecificDate">Specific Time</label>
{timeMode === 2 && <TimePicker value={startTime} onChange={setStartTime} />}
</div>
</div>
</div>
</div>
</div >
<div className="card-footer pt-0 p-3 d-flex align-items-center">
<div className="w-60"> <p className="text-sm"> </p>
</div>
<div className="w-40 text-end">
<a className="btn bg-gradient-primary mb-0 text-end" onClick={SaveData}>Save Details</a>
</div>
</div>
</>
);
}

This is the Calender component

export const Calender = ({name, dates}) => {
const [upcommingDates, setUpcommingDates] = useState([])
useEffect(() =>
{
let dts = [];
for (let i = 0; i < dates.length; i++) {
dts.push(
{ title: name, date: dates[i], className: 'bg-gradient-dark' }
);
}
setUpcommingDates(dts);
}, []);
return (
<div className="card card-calendar">
<div className="card-body p-3">
<FullCalendar
allDayClassNames="calendar"
plugins={[dayGridPlugin]}
initialView="dayGridMonth"
weekends={false}
events={upcommingDates}
/>
</div>
</div>
); }

Answer

I found the issue.

Instead of using “upcommingDates” as dependency to the useEffect() in parent component

I had to put it inside the child component (Calender component), So that it will rerender when dates changes

export const Calender = ({name, dates}) => {
const [upcommingDates, setUpcommingDates] = useState([])
useEffect(() =>
{
let dts = [];
for (let i = 0; i < dates.length; i++) {
dts.push(
{ title: name, date: dates[i], className: 'bg-gradient-dark' }
);
}
setUpcommingDates(dts);
}, [dates]);
return (
<div className="card card-calendar">
<div className="card-body p-3">
<FullCalendar
allDayClassNames="calendar"
plugins={[dayGridPlugin]}
initialView="dayGridMonth"
weekends={false}
events={upcommingDates}
/>
</div>
</div>
);
}