-
Notifications
You must be signed in to change notification settings - Fork 62
Open
Description
I have the following component:
import { CalendarContext } from '@/app/(app)/account/lecture/calendar/calendar-provider';
import { SlotResizer } from '@/app/(app)/account/lecture/calendar/views/slots/slot-resizer';
import { useDragSlot } from '@/app/(app)/account/lecture/calendar/views/slots/use-drag-slot';
import { useResizeSlot } from '@/app/(app)/account/lecture/calendar/views/slots/use-resize-slot';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useTraceChange } from '@/hooks/use-trace-change';
import { formatDate } from '@/lib/utils';
import { LectureSlotRepeatMode, Slot } from '@/types';
import { Clock } from 'lucide-react';
import { memo } from 'react';
import { useContextSelector } from 'use-context-selector';
const Test = memo(({ slot }: { slot: Slot }) => {
console.log('Rerender');
const color = CALENDAR_SLOT_ITEMS.find(
(el) => el.type === slot.repeatMode
)!.color;
const mode = useContextSelector(CalendarContext, (ctx) => ctx.mode);
return <div>DIV</div>;
});
const CALENDAR_SLOT_ITEMS = [
{
type: LectureSlotRepeatMode.DAY,
color: 'green',
},
{
type: LectureSlotRepeatMode.WEEK,
color: 'blue',
},
{
type: LectureSlotRepeatMode.NO_REPEAT,
color: 'violet',
},
];
Test.displayName = 'Test';
export const CalendarSlotEventInner = memo(({ slot }: { slot: Slot }) => {
const color = CALENDAR_SLOT_ITEMS.find(
(el) => el.type === slot.repeatMode
)!.color;
const getEventStyles = useContextSelector(
CalendarContext,
(ctx) => ctx.getEventStyles
);
const mode = useContextSelector(CalendarContext, (ctx) => ctx.mode);
const { height } = getEventStyles(slot);
const {
isResizingBottom,
isResizingTop,
topResizerRef,
bottomResizerRef,
onMouseDownResizer,
} = useResizeSlot(slot);
const { dragging, slotRef, onMouseDown, newTop, translateX } =
useDragSlot(slot);
return (
<div
ref={slotRef}
className='absolute z-[4] flex w-full flex-col gap-1 overflow-hidden border-x-[3px]'
style={{
top: newTop,
height,
borderColor: `var(--${color}8)`,
backgroundColor: `var(--${color}1)`,
transform: translateX ? `translateX(${translateX}px)` : undefined,
}}
>
{mode === 'slots' && (
<SlotResizer
onMouseDown={(e) => onMouseDownResizer(e, true)}
ref={topResizerRef}
dir='up'
slot={slot}
/>
)}
<ScrollArea
className='flex-1'
onMouseDown={onMouseDown}
style={{
cursor:
isResizingTop || isResizingBottom
? 'ns-resize'
: dragging
? 'grabbing'
: mode === 'slots'
? 'grab'
: 'default',
}}
>
<div className='p-2'>
<span className='flex items-center gap-1.5 text-xs'>
<Clock className='h-4 w-4 shrink-0' />
<span className='font-bold'>
{formatDate(slot.fromDate, 'HH:mm')} -{' '}
{formatDate(slot.toDate, 'HH:mm')}
</span>
</span>
<span className='flex items-center gap-1.5'>
{/* <Repeat className='h-4 w-4 shrink-0' /> */}
{/* {slot.repeatMode === LectureSlotRepeatMode.NO_REPEAT
? 'Brak'
: slot.repeatMode === LectureSlotRepeatMode.WEEK
? '1 W'
: '1 BD'} */}
{/* <span>
{
getSelectableRepeatModes().find(
(el) => el.value === slot.repeatMode
)?.label
}
</span> */}
</span>
</div>
</ScrollArea>
{mode === 'slots' && (
<SlotResizer
ref={bottomResizerRef}
slot={slot}
dir='down'
onMouseDown={(e) => onMouseDownResizer(e, false)}
/>
)}
</div>
);
});
CalendarSlotEventInner.displayName = 'CalendarSlotEventInner';
export const CalendarSlotEvent = memo(({ slot }: { slot: Slot }) => {
if (slot.id !== '69fc4516-970c-4a00-92f1-3fa073cbe4d8')
return <Test slot={slot} />;
return <CalendarSlotEventInner slot={slot} />;
// return (
// <Popover>
// <PopoverTrigger asChild>
// </PopoverTrigger>
// <PopoverContent
// className='p-0'
// onOpenAutoFocus={(e) => e.preventDefault()}
// >
// <SlotCard slot={slot} editable={mode === 'slots'} />
// </PopoverContent>
// </Popover>
// );
});
What is happening here is the following:
CalendarSlotEvent
is my main component and I render it for every event in my calendar- I want to render
CalendarSlotEventInner
for every slot but there are renderes that I dont understand so I render CalendarSlotEventInner only for single event (to be able to resize it) and for others I renderTest
component.
When I resize any event, I dont want to rerender other event. For that Im using use-context-selector
to prevent rerenders when value from context does not change, but Test still rerenders... When I remove
const mode = useContextSelector(CalendarContext, (ctx) => ctx.mode);
then Test
is correctly not rerendered. In my contxt I have:
const getEventStyles = useCallback(
(event: { fromDate: Date; toDate: Date }) => {
const fromDate = event.fromDate;
const fromMinutes =
fromDate.getHours() * 60 + fromDate.getMinutes() - hourRange[0] * 60;
const top = fromMinutes * minuteHeight;
const height =
differenceInMinutes(event.toDate, event.fromDate) * minuteHeight;
return { top, height };
},
[hourRange, minuteHeight] // zależności, jeśli się zmieniają, to funkcja się zaktualizuje
);
where
const [mode, setMode] = useState<CalendarMode>(
user.role === Role.TEACHER ? 'both' : 'bookings'
);
and my context returns
<DndProvider backend={HTML5Backend}>
<CalendarContext.Provider
value={{
bookings: bookings
? bookings.results.filter(
(el) => el.status !== BookingStatus.CANCELLED
)
: [],
slots,
setSlots,
date,
setDate,
days,
view,
setView,
dateRange,
setDateRange,
onDateChange,
isFullscreenMode,
setIsFullscreenMode,
hourHeight,
setHourHeight,
hourRange,
setHourRange,
hourScale,
showSlots,
setShowSlots,
showBookings,
setShowBookings,
getEventStyles,
minuteHeight,
mode,
onModeChange,
stepLength,
setStepLength,
showWeekend,
setShowWeekend,
}}
>
{children}
</CalendarContext.Provider>
</DndProvider>
Any suggestions why the Test
component rerenders? As you can see my context is huge and chatGPT suggested that the reference to mode
is changing each time (during resize, slots
state i updated) and this is beacuse use-context-selector
does not work properly. But how then fix that? I dont want to have 10 smaller context for every state :(
Metadata
Metadata
Assignees
Labels
No labels