I am trying to make a planner, which has buttons to switch months. I am using react and states to store dates. The individual months are being mapped properly, but switching between months gives some strange behaviour. The months update, but the dates don't seem to be being updated and the button has to be pressed multiple times for the map to update. Pressing the other button then jumps two months and the same behaviour occurs.
const MonthView = () => {
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const daysOfWeek = ["Sun", "Mon", "Tues", "Wed", "Thu", "Fri", "Sat"];
const globalDate = new Date();
// ^ switches between two months. strange behaviour. fix?
// date does not appear to actially change.
const [month, setMonth] = useState(globalDate.getMonth());
const [year, setYear] = useState(globalDate.getFullYear());
const [day, setDay] = useState(globalDate.getDate());
const [dayOfWeek, setDayOfWeek] = useState(globalDate.getDay());
const [days, setDays] = useState([]);
const [events, setEvents] = useState([]);
const [firstDayOfMonthDay, setFirstDayOfMonthDay] = useState(daysOfWeek[new Date(year, month, 1).getDay()]);
const [firstDayOfMonth, setFirstDayOfMonth] = useState(new Date(year, month, 1).getDay());
const [lastDayOfMonth, setLastDayOfMonth] = useState(new Date(year, month 1, 0).getDay());
const [lastDayOfMonthNumber, setLastDayOfMonthNumber] = useState(new Date(year, month 1, 0).getDate());
const [firstDayOfMonthNumber, setFirstDayOfMonthNumber] = useState(new Date(year, month, 1).getDate());
const updateStates = () => {
console.log("gdate: " globalDate);
setMonth(globalDate.getMonth());
setYear(globalDate.getFullYear());
setDay(globalDate.getDate());
setDayOfWeek(globalDate.getDay());
setFirstDayOfMonthDay(daysOfWeek[new Date(year, month, 1).getDay()]);
setFirstDayOfMonth(new Date(year, month, 1).getDay());
setLastDayOfMonth(new Date(year, month 1, 0).getDay());
setLastDayOfMonthNumber(new Date(year, month 1, 0).getDate());
setFirstDayOfMonthNumber(new Date(year, month, 1).getDate());
set_days();
}
const set_days = () => {
let days = [];
for (let i = 0; i < firstDayOfMonth; i ) {
days.push(null);
}
for (let i = 1; i <= lastDayOfMonthNumber; i ) {
days.push(i);
}
for (let i = 0; i < (6 - lastDayOfMonth); i ) {
days.push(i 1);
}
setDays(days);
console.log(days)
console.log(globalDate);
}
useEffect(() => {
set_days();
}, []);
return(
<>
<Table striped bordered hover size="sm" variant="light">
<thead>
<tr>
<th></th>
<th></th>
<th><Button variant="info" onClick={() => {globalDate.setMonth(globalDate.getMonth() - 1); updateStates();}}><FontAwesomeIcon icon={faAngleLeft} /></Button></th>
<th>{months[month]}</th>
<th><Button variant="info" onClick={() => {globalDate.setMonth(globalDate.getMonth() 1); updateStates();}}><FontAwesomeIcon icon={faAngleRight} /></Button></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
{daysOfWeek.map((day, index) => {
return <td key={index}>{day}</td>
}
)}
</tr>
{days.map((day, index) => {
if (index % 7 === 5) {
// MAKE CLICK ON CELL RETURN VAL and create new event on that day
// also week view and day view
// use lighthouse to test and do everything possible to decrease load time
return <tr key={index}> {days.slice(index - 5, index 2).map((day, index2) => {
return <td key={index2} onClick={() => {console.log(index index2 - 5);}}>{day}</td>
}
)}
</tr>
}
}
)}
</tbody>
</Table>
</>
)
}
CodePudding user response:
As commenter stefan mentioned, we can greatly simplify this code to reduce the possibility of errors. You have a lot of state variables that are calculated from globalDate
. If you can always calculate a value from other state, you should not store it as state. You can simply declare them as constants. You no longer need your updateStates
logic, and your set_days
function doesn't need to be in a useEffect
hook. After removing redundant state, the only state variables you really need are globalDate
and events
.
Another issue is your button onClick
behavior. When you call globalDate.setMonth()
, you are mutating state. State in React is immutable and should be updated by creating a new copy of state and changing the parts you need. To remedy this, you can instantiate a new Date
object, call setMonth
on that object, then set globalDate
to that new object.
The following simplifies your code and fixes the issue you are experiencing:
const MonthView = () => {
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const daysOfWeek = ["Sun", "Mon", "Tues", "Wed", "Thu", "Fri", "Sat"];
const [events, setEvents] = useState([]);
const [globalDate, setGlobalDate] = useState(new Date());
const month = globalDate.getMonth();
const year = globalDate.getFullYear();
const day = globalDate.getDate();
const dayOfWeek = globalDate.getDay();
const firstDayOfMonthDay = daysOfWeek[new Date(year, month, 1).getDay()];
const firstDayOfMonth = new Date(year, month, 1).getDay();
const lastDayOfMonth = new Date(year, month 1, 0).getDay();
const lastDayOfMonthNumber = new Date(year, month 1, 0).getDate();
const firstDayOfMonthNumber = new Date(year, month, 1).getDate();
const days = (() => {
let daysArr = [];
for (let i = 0; i < firstDayOfMonth; i ) {
daysArr.push(null);
}
for (let i = 1; i <= lastDayOfMonthNumber; i ) {
daysArr.push(i);
}
for (let i = 0; i < 6 - lastDayOfMonth; i ) {
daysArr.push(i 1);
}
return daysArr;
})();
return (
<>
<Table>
<thead>
<tr>
<th></th>
<th></th>
<th>
<Button
variant="info"
onClick={() => {
const newDate = new Date();
newDate.setMonth(globalDate.getMonth() - 1);
setGlobalDate(newDate);
}}
>
<FontAwesomeIcon icon={faAngleLeft} />
</Button>
</th>
<th>{months[month]}</th>
<th>
<Button
variant="info"
onClick={() => {
const newDate = new Date();
newDate.setMonth(globalDate.getMonth() 1);
setGlobalDate(newDate);
}}
>
<FontAwesomeIcon icon={faAngleRight} />
</Button>
</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
{daysOfWeek.map((day, index) => {
return <td key={index}>{day}</td>;
})}
</tr>
{days.map((day, index) => {
if (index % 7 === 5) {
// MAKE CLICK ON CELL RETURN VAL and create new event on that day
// also week view and day view
// use lighthouse to test and do everything possible to decrease load time
return (
<tr key={index}>
{" "}
{days.slice(index - 5, index 2).map((day, index2) => {
return (
<td
key={index2}
onClick={() => {
console.log(index index2 - 5);
}}
>
{day}
</td>
);
})}
</tr>
);
}
})}
</tbody>
</Table>
</>
);
};
You have some unused variables that I left in just in case you need them later. It looks like some values like firstDayOfMonth
, lastDayOfMonth
, and lastDayOfMonthNumber
are used only in the function that generates the days
array, so they can probably be moved into the body of that function if they aren't needed elsewhere.