Usecase: at the top level, my app has a tab navigator with a Calendar tab and a Camera tab. In the Calendar tab, it displays an infinitely scrollable CalendarList using react-native-calendars
. The app state maintains an object with date strings as keys and sorted arrays of images as values. The calendar's parent view passes it a memoized object containing the last image for each date via context:
const latestImagesByDateString = useMemo(
() => mapLatestImagesByDateString(imagesByDateString),
[imagesByDateString]
)
// ...
<ImageContext.Provider value={latestImagesByDateString}>
<Stack.Screen
name="CalendarView"
component={CalendarView}
/>
</ImageContext.Provider>
The CalendarView renders a component for each date, which may have an image:
const getDayComponent = useCallback(
({ date, state }) => (
<CalendarDayView
imageUri={latestImagesByDateString[date.dateString]?.uri}
imageId={latestImagesByDateString[date.dateString]?.id}
isToday={state === 'today'}
dateString={date.dateString}
day={date.day}
/>
),
[latestImagesByDateString]
)
// ...
return (
<CalendarList
dayComponent={getDayComponent}
/>
)
The Calendar tab has a stack navigator which renders a detail view for a given day when it gets pressed. This is all working as intended.
However, if I navigate to the Camera tab, the user can take a photo, which they can then save or discard. If they save the photo, I update app state with the new photo, then navigate back to the Calendar tab's detail view for that day, showing the new photo.
This also works as intended, except: when I save the photo and navigate to the appropriate detail view, the Calendar will re-render every date in its grid before the detail view gets displayed. This causes a long delay between pressing the "Save photo" button and the detail view being rendered onscreen. When a user saves a new photo, all of the dayComponents except one should be the same as on the previous render. I can't figure out why all the other dayComponents are re-rendering in this case. How can I prevent that from happening?
Update: I solved it. I found the function in the react-native-calendars
library where the Day components were being compared for equality and logged the prop name when they weren't equal — this showed me that the dayComponent
prop was always a different function when app state got updated. My CalendarDayView
component was wrapped in React.memo
, but when I added an equality function there, it wasn't being called.
So instead of using a getDayComponent
function, I created a new component called CalendarDayViewWrapper
, which I pass to the dayComponent
prop of CalendarList
. The wrapper component's props are date
and state
, and it determines the correct imageUri
etc before returning the memoized CalendarDayView
component. Now the unnecessary re-renders are gone!