I am struggling to stop my entire List from rerendering. When I select an item it should be underlined. It should suffice if the newly selected and the previously selected elements are rerendered.
In a worst-case scenario, this List can get hundreds of entries long and it's getting really slow. So I tried using React.memo
method, but I am probably using it wrong.
These are my custom Functions for List and ListEntry (changeSelected and currentlySelected are the state and setState functions from the parent)
function CustomList(props: any) {
return (
<List
component="div"
>
{props.data.map((entryData: any, index: number) => {
return (
<React.Fragment key={index}>
<CustomListEntry
entryData={entryData}
changeSelected={props.changeSelected}
style={{
borderBottom:
props.currentlySelected == entryData.id ? "dashed" : "",
}}
/>
</React.Fragment>
);
})}
</List>
);
}
const CustomListEntry = React.memo((props: any) => {
return (
<ListItemButton
style={props.style}
onClick={() => {
props.changeSelected(props.entryData.id);
}}
>
<ListItemText>
{props.entryData.exampledata}
</ListItemText>
</ListItemButton>
);
});
And the parent Component:
function Root(props:any){
const [selectedId, setSelectedId] = React.useState("");
...
return{
<CustomList
data={data}
changeSelected={setSelectedId}
currentlySelected={selectedId}
></CustomList>
}
}
Edit: Here my full code, if its important for context. (useApiEnpoint just uses Axios to get the initial data for the List. Is just called on first mounting and shouldnt affect the poor performance on selection change)
export default function SubSiteDokumentation(props: any) {
const StammdatenContext = React.useContext(ProbandContext);
const [selectedId, setSelectedId] = React.useState("");
const { formData, handleGetDetail, isLoading, isError } = useAPIEndpoint({
endpoint: "KontaktetermineList",
formName: "Termine",
pk: StammdatenContext.currentPnr,
enableSnackbar: true,
initialLoading: true,
});
React.useEffect(() => {
handleGetDetail();
}, []);
if (isError) {
return (
<Typography variant="h4" align="center" color="red">
Termine konnten nicht geladen werden! Versuchen sie es später erneut
oder wenden sie sich an den Administrator
</Typography>
);
}
if (isLoading) {
return <Spinner animation="border"></Spinner>;
}
return (
<Grid container spacing={2}>
<Grid item xs={7}>
<TermineList
data={formData}
changeSelected={setSelectedId}
currentlySelected={selectedId}
></TermineList>
</Grid>
{/* <Grid item xs={5}>
<TerminDetail currentlySelected={selectedId}></TerminDetail>
</Grid> */}
</Grid>
);
}
function TermineList(props: any) {
return (
<Box
pl={"10px"}
sx={{
background: "#b4f5bf",
}}
className="my-sidebarAuftraege my-nice-scrollbarBox"
>
<ListSubheader sx={{ background: "#b4f5bf", zIndex: "1000" }}>
<Typography align="center" variant="h5" color="black">
Dokumentation
</Typography>
</ListSubheader>
<List
component="div"
disablePadding
sx={{ background: "#e3ffe9", borderRadius: "10px" }}
>
{props.data.map((termin: any, index: number) => {
let style = {
backgroundColor: index % 2 === 1 ? "#00000011" : "",
borderColor: "#89ca07",
borderBottom: props.currentlySelected == termin.id ? "dashed" : "",
};
return (
<React.Fragment key={index}>
<TermineListEntry
termin={termin}
changeSelected={props.changeSelected}
style={style}
/>
<Divider
sx={{ borderBottomWidth: 2, backgroundColor: "black" }}
></Divider>
</React.Fragment>
);
})}
<ListItem
sx={{
display: "flex",
justifyContent: "center",
backgroundColor:
(props.data.length % 2 === 1 && "#00000011") || "#00000000",
}}
>
{/* <Box sx={{ display: "flex", justifyContent: "center" }}>
<div>test</div>
</Box> */}
</ListItem>
</List>
</Box>
);
}
const TermineListEntry = React.memo((props: any) => {
return (
<ListItemButton
style={props.style}
onClick={() => {
props.changeSelected(props.termin.id);
}}
>
<ListItemText>
<Grid container>
<Grid item xs={2}>
{Moment(props.termin.datum).format("DD.MM.YYYY")}
</Grid>
<Grid item xs={2}>
{props.termin.uhrzeit}
</Grid>
<Grid item xs={6}>
{props.termin.inhalt}
</Grid>
<Grid item xs={1}>
{props.termin.art}
</Grid>
<Grid item xs={1}>
{props.termin.kv}
</Grid>
</Grid>
</ListItemText>
</ListItemButton>
);
});
.my-sidebarAuftraege {
height: calc(
calc(calc(100vh - #{$navbar-height}) - #{$subNavbar-height}) - 24px
);
padding: 0px;
border-style: solid;
border-color: $primary-dark;
background-color: #00000009;
border-radius: 5px;
overflow: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
//Scrollbar Styling for Edge
.my-nice-scrollbarBox::-webkit-scrollbar {
width: 10px;
height: 6px;
}
.my-nice-scrollbarBox::-webkit-scrollbar-track {
background: transparent;
}
.my-nice-scrollbarBox::-webkit-scrollbar-thumb {
border-radius: 10px;
background: rgba(0, 0, 0, 0.2);
}
.my-nice-scrollbarBox::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.4);
}
.my-nice-scrollbarBox::-webkit-scrollbar-thumb:active {
background: rgba(0, 0, 0, 0.9);
}
//Scrollbar Styling for Firefox
.my-nice-scrollbarBox {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
}
CodePudding user response:
The reason is simple : when you change the state on the parent level, the parent will rerender, forcing rerendering of all child elements.
Note that this should not be too long anyway so you probably have another performance issue somewhere :) (the DOM is not updated by react) - e.g. having console.log(..) somewhere
Here is a code pen with 10k buttons in the list, still you can see that the rendering is pretty quick: https://codepen.io/aSH-uncover/pen/rNKWaKE
for (let i = 0 ; i < 10000 ; i ) {
data.push({ id: String(i), exampledata: `Value ${i}`})
}
If you really need to get rid of the whole rendering you will have to take another approach such as a global store with one entry per button and stop storing the selectedId in the parent state :)
CodePudding user response:
You use React.memo
correctly, but you need to modify CustomList
a bit:
- Shouldn't use
index
as a key, because whenprops.data
changes,index
may change as well that will make the entire list re-rendered - You don't need to wrap
React.Fragment
and movekey
toCustomListEntry
element which would help to recognize element changes style
with{}
may cause re-renderings, you should put the condition outside to prevent initializing new objects. Note that you can useclassName
to customize styles which is a more common way
function CustomList(props: any) {
return (
<List component="div">
{props.data.map((entryData: any) => (
<CustomListEntry
entryData={entryData}
changeSelected={props.changeSelected}
key={entryData.id}
style={
props.currentlySelected === entryData.id && {
borderBottom: "dashed",
}
}
/>
))}
</List>
);
}