'Prevent rerender flatlist React Native
I have custom created calendar using Flatlist. In the parent component I have a state with starting date and ending date and Press handler function to update state when user presses on the date. The problem is every time when I press the date render function invokes every time.
The question is: How to keep state to change, but not rerender whole calendar again and again?
Parent component with FlatList.
interface Props {
arrivalDate: string | undefined;
departureDate: string | undefined;
onDayPress: (day: Date) => void;
futureYearRange?: number;
}
const CustomCalendarList: React.FC<Props> = ({
arrivalDate,
departureDate,
futureYearRange = 5,
onDayPress,
}) => {
const months = useMonths();
const [isLoading, setIsLoading] = useState(true);
const [dates, setDates] = useState({
endDate: arrivalDate,
startDate: departureDate,
});
const handleDayPress= useCallback((row:IRow) => (e?: GestureResponderEvent) => {
if (!dates.startDate || (dates.startDate && dates.endDate)) {
setDates({endDate: undefined, startDate: row.date});
} else {
setDates(prevState => ({...prevState, endDate: row.date}))
}
}, [setDates]);
const { grids, monthsToRender } = useMemo(() => {
const monthToRender = 11 - dayjs().month() + futureYearRange;
const monthsToRender: Array<{ title: string; year: number }> = [];
const grids = [];
for (let i = 0; i < monthToRender; i++) {
const newGrid: Array<Array<IRow>> = [];
const date = dayjs().add(i, "month");
const daysInMonth = dayjs(date).daysInMonth();
const monthIndex = dayjs(date).month();
const year = dayjs(date).year();
monthsToRender.push({ title: months[monthIndex], year });
for (let j = 0; j < daysInMonth - 1; j++) {
let row = [];
// minus 1 because by default in dayjs start week day is sunday(index=0)
let startingIndex = j === 0 ? dayjs(date).startOf("month").day() - 1 : 0;
startingIndex = startingIndex === -1 ? 6 : startingIndex;
for (let k = startingIndex; k < 7; k++) {
if (!(j + 1 > daysInMonth)) {
row[k] = {
day: j + 1,
date: dayjs(date)
.date(j + 1)
.format("YYYY-MM-DD"),
};
}
if (k === 6) {
newGrid.push(row);
} else {
j += 1;
}
}
}
grids.push(newGrid);
};
console.log('generated')
return {
grids,
monthsToRender
};
}, [futureYearRange]);
const renderItem = useCallback(({
item,
index,
}: ListRenderItemInfo<Array<Array<IRow>>>) => {
return (
<Grid
onPress={handleDayPress}
monthsToRender={monthsToRender}
grid={item}
gridIndex={index}
/>
);
}, [dates.startDate, dates.endDate]);
useEffect(() => {
const timeoutId = setTimeout(() => {
setIsLoading(false);
}, 300);
return () => {
clearTimeout(timeoutId);
};
}, []);
if (isLoading) {
return (
<View
style={css`
height: 90%;
justify-content: center;
align-items: center;
background: ${colors.primaryBg};
`}
>
<ActivityIndicator color={"blue"} size="large" />
</View>
);
}
return (
<Calendar>
<FlatList
data={grids}
showsVerticalScrollIndicator={false}
updateCellsBatchingPeriod={1000}
renderItem={renderItem}
maxToRenderPerBatch={3}
keyExtractor={() => uuidv4()}
/>
</Calendar>
);
};
Solution 1:[1]
Issue
You are generating new React keys each time the component renders.
<FlatList
data={grids}
showsVerticalScrollIndicator={false}
updateCellsBatchingPeriod={1000}
renderItem={renderItem}
maxToRenderPerBatch={3}
keyExtractor={() => uuidv4()} // <-- new React key each render cycle!
/>
With non-stable keys React assumes these are all new elements and need to be mounted and rendered. Using the array index would be a better solution (don't do that though!!).
Solution
Add the generated GUID as a property that can then be extracted when rendering.
Example:
const { grids, monthsToRender } = useMemo(() => {
...
const grids = [];
for (let i = 0; i < monthToRender; i++) {
...
for (let j = 0; j < daysInMonth - 1; j++) {
...
for (let k = startingIndex; k < 7; k++) {
if (!(j + 1 > daysInMonth)) {
row[k] = {
guid: uuidV4(), // <-- generate here
day: j + 1,
date: dayjs(date)
.date(j + 1)
.format("YYYY-MM-DD")
};
}
if (k === 6) {
newGrid.push(row);
} else {
j += 1;
}
}
}
grids.push(newGrid);
}
return {
grids,
monthsToRender
};
}, [futureYearRange]);
...
<FlatList
data={grids}
showsVerticalScrollIndicator={false}
updateCellsBatchingPeriod={1000}
renderItem={renderItem}
maxToRenderPerBatch={3}
keyExtractor={({ guid }) => guid} // <-- extract here
/>
Solution 2:[2]
I think it might have to do with the dependencies you are defining for some of your useCallback hooks. The second parameter to useCallback should be an array of variables you reference inside the hook. For example:
const foo = Math.random() > 0.5 ? 'bar' : 'bing';
const someCallback = useCallback(() => {
if (foo === 'bar') { ... }
}, [foo]); // you must list "foo" here because it's referenced in the callback
There is one exception: state setters are not required as they are guaranteed to never change::
React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the
useEffectoruseCallbackdependency list.
https://reactjs.org/docs/hooks-reference.html#usestate
const [foo, setFoo] = useState('bar');
const someCallback = useCallback(() => {
setFoo('bing');
}, []); // there is no need to put `setFoo` here - WOOHOO!
Fixing your code:
You have several situations where you are not listing all of the referenced variables, which can get you into a very undetermined situation.
handleDayPress- you can removesetDatesfrom the dependency list and adddates.const handleDayPress= useCallback(..., [dates]);The memo looks good! It only needs
futureYearRangerenderItem- remove the current depencies and addhandleDayPressandmonthsToRenderconst renderItem = useCallback(..., [handleDayPress, monthsToRender]);
Solution 3:[3]
So, one of the way to solve a problem is to check props via React.memo in Grid component to prevent unnecessary months render. Grid component in my case is whole month to render, so if I check for startDate or endDate in a month it will render only that months.
The problem is only when user wants to select the startDate and endDate within 5+ months difference. There will cause 5 renders again. Let's imagine if it will be 10+ months difference, it will start freezing.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Drew Reese |
| Solution 2 | Ryan Wheale |
| Solution 3 | Exoriri |

