Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize app by reducing rerenders #1074

Merged
merged 14 commits into from
Dec 11, 2024
69 changes: 69 additions & 0 deletions apps/antalmanac/src/components/Calendar/CalendarCourseEvent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Box } from '@material-ui/core';
import { memo } from 'react';
import { shallow } from 'zustand/shallow';

import { CalendarEvent } from '$components/Calendar/CourseCalendarEvent';
import locationIds from '$lib/location_ids';
import { useSelectedEventStore } from '$stores/SelectedEventStore';

export const CalendarCourseEvent = memo(({ event }: { event: CalendarEvent }) => {
const setSelectedEvent = useSelectedEventStore((state) => state.setSelectedEvent, shallow);

const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

setSelectedEvent(e, event);
};

if (event.isCustomEvent) {
return (
<Box onClick={handleClick}>
<Box
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
fontWeight: 500,
fontSize: '0.8rem',
}}
>
<Box>{event.title}</Box>
</Box>

<Box style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', fontSize: '0.7rem' }}>
<Box>{Object.keys(locationIds).find((key) => locationIds[key] === parseInt(event.building))}</Box>
</Box>
</Box>
);
}

return (
<Box onClick={handleClick}>
<Box
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
fontWeight: 500,
fontSize: '0.8rem',
}}
>
<Box>{event.title}</Box>
<Box style={{ fontSize: '0.8rem' }}> {event.sectionType}</Box>
</Box>
<Box style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', fontSize: '0.7rem' }}>
<Box>
{event.showLocationInfo
? event.locations.map((location) => `${location.building} ${location.room}`).join(', ')
: event.locations.length > 1
? `${event.locations.length} Locations`
: `${event.locations[0].building} ${event.locations[0].room}`}
</Box>
<Box>{event.sectionCode}</Box>
</Box>
</Box>
);
});

CalendarCourseEvent.displayName = 'CalendarCourseEvent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Box } from '@mui/material';
import { useCallback, useEffect, useRef } from 'react';
import { EventWrapperProps } from 'react-big-calendar';
import { shallow } from 'zustand/shallow';

import type { CalendarEvent } from '$components/Calendar/CourseCalendarEvent';
import { useSelectedEventStore } from '$stores/SelectedEventStore';

interface CalendarCourseEventWrapperProps extends EventWrapperProps<CalendarEvent> {
children?: React.ReactNode;
}

/**
* CalendarCourseEventWrapper allows us to override the default onClick event behavior which problamtically rerenders the entire calendar
*/
export const CalendarCourseEventWrapper = ({ children, ...props }: CalendarCourseEventWrapperProps) => {
const ref = useRef<HTMLDivElement>(null);

const setSelectedEvent = useSelectedEventStore((state) => state.setSelectedEvent, shallow);

const handleClick = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

setSelectedEvent(e, props.event);
},
[props.event, setSelectedEvent]
);

useEffect(() => {
const node = ref.current;
if (!node) {
return;
}

const rbcEvent = node.querySelector('.rbc-event') as HTMLDivElement;
if (!rbcEvent) {
return;
}

rbcEvent.onclick = (e) => handleClick(e as unknown as React.MouseEvent); // the native onclick requires a little type hacking
}, [handleClick]);

return <Box ref={ref}>{children}</Box>;
};
58 changes: 58 additions & 0 deletions apps/antalmanac/src/components/Calendar/CalendarEventPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Popover } from '@mui/material';
import { useCallback, useEffect, useState } from 'react';
import { shallow } from 'zustand/shallow';

import CourseCalendarEvent from '$components/Calendar/CourseCalendarEvent';
import AppStore from '$stores/AppStore';
import { useSelectedEventStore } from '$stores/SelectedEventStore';

export function CalendarEventPopover() {
const [anchorEl, selectedEvent, setSelectedEvent] = useSelectedEventStore(
(state) => [state.selectedEventAnchorEl, state.selectedEvent, state.setSelectedEvent],
shallow
);

const [scheduleNames, setScheduleNames] = useState(() => AppStore.getScheduleNames());

const handleClosePopover = useCallback(() => {
setSelectedEvent(null, null);
}, [setSelectedEvent]);

useEffect(() => {
const updateScheduleNames = () => {
setScheduleNames(AppStore.getScheduleNames());
};

AppStore.on('scheduleNamesChange', updateScheduleNames);

return () => {
AppStore.off('scheduleNamesChange', updateScheduleNames);
};
}, []);

if (!selectedEvent) {
return null;
}

return (
<Popover
anchorEl={anchorEl}
open={Boolean(anchorEl) && !!selectedEvent}
onClose={handleClosePopover}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<CourseCalendarEvent
closePopover={handleClosePopover}
selectedEvent={selectedEvent}
scheduleNames={scheduleNames}
/>
</Popover>
);
}
Loading
Loading