-
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:
CalendarSlotEventis my main component and I render it for every event in my calendar- I want to render
CalendarSlotEventInnerfor 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 renderTestcomponent.
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