-
Notifications
You must be signed in to change notification settings - Fork 56
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
Render activity list items fully first when they enter viewport #2417
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import { FilterListOutlined } from '@mui/icons-material'; | ||
import { FilterListOutlined, Pending } from '@mui/icons-material'; | ||
import Fuse from 'fuse.js'; | ||
import { useMemo } from 'react'; | ||
import { Box, Card, Divider, Typography } from '@mui/material'; | ||
import { useEffect, useMemo, useRef, useState } from 'react'; | ||
import { Box, BoxProps, Card, Divider, Typography } from '@mui/material'; | ||
|
||
import CallAssignmentListItem from './items/CallAssignmentListItem'; | ||
import EmailListItem from './items/EmailListItem'; | ||
|
@@ -17,12 +17,61 @@ import useClusteredActivities, { | |
CLUSTER_TYPE, | ||
} from 'features/campaigns/hooks/useClusteredActivities'; | ||
import CanvassAssignmentListItem from './items/CanvassAssignmentListItem'; | ||
import ActivityListItem, { STATUS_COLORS } from './items/ActivityListItem'; | ||
|
||
interface ActivitiesProps { | ||
activities: CampaignActivity[]; | ||
orgId: number; | ||
} | ||
|
||
interface LazyActivitiesBoxProps extends BoxProps { | ||
index: number; | ||
} | ||
|
||
const LazyActivitiesBox = ({ | ||
index, | ||
children, | ||
...props | ||
}: LazyActivitiesBoxProps) => { | ||
const [inView, setInView] = useState(false); | ||
const boxRef = useRef<HTMLElement>(); | ||
|
||
useEffect(() => { | ||
if (!boxRef.current) { | ||
return; | ||
} | ||
|
||
const observer = new IntersectionObserver((entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting) { | ||
setInView(true); | ||
} | ||
}); | ||
}); | ||
|
||
observer.observe(boxRef.current); | ||
}, [boxRef]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to unobserve or destroy the observer when the component unmounts, or will that be handled automatically by the browser when React removes the element from the DOM? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I can find in discussions the intent is for garbage collection to deal with it if observer is no longer observing any elements (using weak references): |
||
|
||
return inView ? ( | ||
<Box {...props}> | ||
{index > 0 && <Divider />} | ||
{children} | ||
</Box> | ||
) : ( | ||
<Box ref={boxRef}> | ||
{index > 0 && <Divider />} | ||
<ActivityListItem | ||
color={STATUS_COLORS.GRAY} | ||
endNumber={0} | ||
href={'#'} | ||
PrimaryIcon={Pending} | ||
SecondaryIcon={Pending} | ||
title={'...'} | ||
/> | ||
</Box> | ||
); | ||
}; | ||
|
||
const Activities = ({ activities, orgId }: ActivitiesProps) => { | ||
const clustered = useClusteredActivities(activities); | ||
|
||
|
@@ -31,52 +80,52 @@ const Activities = ({ activities, orgId }: ActivitiesProps) => { | |
{clustered.map((activity, index) => { | ||
if (activity.kind === ACTIVITIES.CALL_ASSIGNMENT) { | ||
return ( | ||
<Box key={`ca-${activity.data.id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox key={`ca-${activity.data.id}`} index={index}> | ||
<CallAssignmentListItem caId={activity.data.id} orgId={orgId} /> | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} else if (activity.kind == ACTIVITIES.CANVASS_ASSIGNMENT) { | ||
return ( | ||
<Box key={`canvassassignment-${activity.data.id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox | ||
key={`canvassassignment-${activity.data.id}`} | ||
index={index} | ||
> | ||
<CanvassAssignmentListItem | ||
caId={activity.data.id} | ||
orgId={orgId} | ||
/> | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} else if (isEventCluster(activity)) { | ||
return ( | ||
<Box key={`event-${activity.events[0].id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox | ||
key={`event-${activity.events[0].id}`} | ||
index={index} | ||
> | ||
{activity.kind == CLUSTER_TYPE.SINGLE ? ( | ||
<EventListItem cluster={activity} /> | ||
) : ( | ||
<EventClusterListItem cluster={activity} /> | ||
)} | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} else if (activity.kind === ACTIVITIES.SURVEY) { | ||
return ( | ||
<Box key={`survey-${activity.data.id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox key={`survey-${activity.data.id}`} index={index}> | ||
<SurveyListItem orgId={orgId} surveyId={activity.data.id} /> | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} else if (activity.kind === ACTIVITIES.TASK) { | ||
return ( | ||
<Box key={`task-${activity.data.id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox key={`task-${activity.data.id}`} index={index}> | ||
<TaskListItem orgId={orgId} taskId={activity.data.id} /> | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} else if (activity.kind === ACTIVITIES.EMAIL) { | ||
return ( | ||
<Box key={`email-${activity.data.id}`}> | ||
{index > 0 && <Divider />} | ||
<LazyActivitiesBox key={`email-${activity.data.id}`} index={index}> | ||
<EmailListItem emailId={activity.data.id} orgId={orgId} /> | ||
</Box> | ||
</LazyActivitiesBox> | ||
); | ||
} | ||
})} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm so glad you introduced this solution. I was not aware of
IntersectionObserver
(like I was withResizeObserver
and observers in general) so I had to look it up, but it's covered by all browsers that we care about so I think it's the ultimate solution for us.