fix: call function renderItem directly to prevent remount on data updates#441
Conversation
…ates When renderItem is passed as a plain function (the common callback pattern), using React.createElement treats the function reference as a component type. If that reference changes between renders (e.g. on a data update like voting), React sees a different component type and unmounts/remounts the entire cell subtree, causing visible flashing of images and other content. Restore the isFunction check so plain functions are called directly, while class components still go through createElement.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16edeaf6fe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Always render through React so function components can safely use Hooks. | ||
| renderedItem = React.createElement(renderItem, itemProps) as React.ReactNode; | ||
| renderedItem = ( | ||
| isFunction(renderItem) ? renderItem(itemProps) : React.createElement(renderItem, itemProps) |
There was a problem hiding this comment.
Preserve React component rendering path for renderItem
Do not invoke renderItem directly when it may be a React component: isFunction(renderItem) is true for both function components and class components, so the React.createElement branch is effectively unreachable. This regresses supported renderItem values from the declared API (React.ComponentType), causing class components to throw when called as plain functions and hook-using function components to fail outside React’s render lifecycle.
Useful? React with 👍 / 👎.
Problem
Since 50ee631,
getRenderedItemalways usesReact.createElement(renderItem, itemProps). This treats therenderItemfunction reference as a React component type. When the reference changes between renders (e.g. inline callbacks, or callbacks with deps that change on data updates), React sees a different component type and unmounts/remounts the entire cell subtree.In practice this causes all images and content in a list to flash on any state change, because every cell is fully destroyed and recreated.
ScreenRecording_05-12-2026.5-53-50.PM_1.mov
Fix
Restore the
isFunctioncheck that was removed in 50ee631. Plain functions are called directly asrenderItem(itemProps).Tradeoff
Function components passed directly as
renderItemthat use hooks internally will no longer work as they need to be wrapped in a callback, so this PR will likely need a few more changes before it's ready to merge.Also I had to modify quite a few of the tests to get it to pass which I don't feel super confident about, ideally we'd want to try and support both uses cases while still fixing the rerender issue. 😅