I'm buildind a React Calendar using Tailwind and Redux and I have a little problem. I have a form where I create a new event and I push this date in my state 'savedEvents'. Atfer that, I want to use events from my state to generate dinamically some spans but my useEffect hook can't rerender when my state is updated and I don't understand why.
GitHub repository for code: https://github.com/snnack123/Events-Calendar
My global state (/store/events.js):
import dayjs from 'dayjs'
const initialState = {
monthIndex: dayjs().month(),
compare: dayjs().month(),
smallCalendarMonth: 0,
daySelected: dayjs(),
showEventModal: false,
savedEvents: [],
}
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, ...state.savedEvents.push(action.payload) };
default:
return state;
}
}
My submit form (/components/EventModal.js):
<form className='bg-white rounded-lg shadow-2xl w-1/4'>
<header className='bg-gray-100 px-4 py-2 flex justify-between items-center'>
<span className='material-icons-outlined text-gray-400'>
drag_handle
</span>
<button onClick={setShowModal}>
<span className='material-icons-outlined text-gray-400'>
close
</span>
</button>
</header>
<div className='p-3'>
<div className="grid grid-cols-1/5 items-end gap-y-7">
<div></div>
<input
type="text"
name="title"
placeholder='Add title'
value={title}
onChange={(e) => setTitle(e.target.value)}
required
className='pt-3 border-0 text-gray-600 text-xl font-semibold pb-2 w-full border-b-2
border-gray-200 focus:outline-none focus:ring-0 focus-blue-500'
/>
<span className='material-icons-outlined text-gray-400'>
schedule
</span>
<p>{daySelected.format('dddd, MMMM DD')}</p>
<span className='material-icons-outlined text-gray-400'>
segment
</span>
<input
type="text"
name="description"
placeholder='Add a description'
value={description}
onChange={(e) => setDescription(e.target.value)}
required
className='pt-3 border-0 text-gray-600 pb-2 w-full border-b-2
border-gray-200 focus:outline-none focus:ring-0 focus-blue-500'
/>
<span className='material-icons-outlined text-gray-400'>
bookmark_border
</span>
<div className="flex gap-x-2">
{labelsClasses.map((lblClass, i) => (
<span key={i} className={`bg-${lblClass}-500 w-6 h-6 rounded-full flex items-center justify-center cursor-pointer`}
onClick={() => setSelectedLabel(lblClass)}>
{selectedLabel === lblClass && (
<span className="material-icons-outlined text-white text-sm">
check
</span>
)}
</span>
))}
</div>
</div>
</div>
<footer className='flex justify-end border-t p-3 mt-5 '>
<button type='button' className='bg-blue-500 hover:bg-blue-600 px-6 py-2 rounded text-white' onClick={saveEvent}>
Save
</button>
</footer>
</form>
Function which update my state from this form:
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const labelsClasses = [
"indigo",
"gray",
"green",
"blue",
"red",
"purple",
];
const [selectedLabel, setSelectedLabel] = useState(labelsClasses[0]);
function saveEvent() {
let new_event = {
title,
description,
label: selectedLabel,
day: daySelected.valueOf(),
id: Date.now(),
};
dispatch({ type: 'events/setNewEvent', payload: new_event });
setShowModal();
}
This is my useEffect that doesn't work (/components/Day.js)
const savedEvents = useSelector((state) => state.events.savedEvents);
const [dayEvents, setDayEvents] = useState([]);
useEffect(() => {
const events = savedEvents.filter(
(evt) =>
dayjs(evt.day).format("DD-MM-YY") === day.format("DD-MM-YY")
);
setDayEvents(events);
}, [savedEvents, day]);
I want to use my state here:
{dayEvents.map((evt, idx) => (
<div
key={idx}
className={`bg-${evt.label}-200 p-1 mr-3 text-gray-600 text-sm rounded mb-1 truncate`}
>
{evt.title}
</div>
))}
CodePudding user response:
The hook UseEffect
is working fine, the source of the problem is this:
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, ...state.savedEvents.push(action.payload) }; // <== This will not trigger the re-rendering of your component
default:
return state;
}
}
Your component will not re-render even the change in the state, because you mutated the array savedEvents
and Redux
will not know that there is a change in the array savedEvents
.
Solution:
export function eventsReducer(state = initialState, action) {
switch (action.type) {
case 'events/setMonthIndex':
return { ...state, monthIndex: action.payload };
case 'events/setCompare':
return { ...state, compare: action.payload };
case 'events/setDaySelected':
return { ...state, daySelected: action.payload };
case 'events/setShowModal':
return { ...state, showEventModal: action.payload };
case 'events/setNewEvent':
return { ...state, savedEvents: [...state.savedEvents, action.payload] }; // <== add this to trigger the re-rendering of your component
default:
return state;
}
}
CodePudding user response:
...state.savedEvents.push(action.payload)
You're mutating your array, so it is the same array and doesn't trigger the effect. Create a new one instead.
savedEvents: [...state.savedEvents, action.payload]