Search code examples
reactjsreact-virtualizedreact-usememoreact-windowreact-memo

How to prevent rerendering of children inside List component of react-window


Problem

I'm trying to achieve a behaviour, when merely changed items of the list are being updated and rerendered with new data.

I wrote a component which is being updated when timer is off:

import { FixedSizeList as List } from 'react-window';


function ProfilesList() {
   ...

   // Here is an effect which fetches new data every time and component is rerendered with new data

   React.useEffect(() => {
        const timer = setInterval(() => {
            dispatch(loadProfilesList());
        }, intervalTime);
        return () => {
            clearInterval(timer);
        };
    }, [dispatch]);

    ...

    return (
       <List height={758} width={625} itemSize={82} itemCount={filteredProfiles.length}>
            {({ index, style }: { index: number; style: any }): React.ReactElement => {
                return (
                    <div style={style} key={index}>
                        <Tooltip title={t`stop` as string}>
                            <Button>lalla</Button>
                        </Tooltip>
                    </div>
                );
            }}
        </List>
    );

And as a result, when timer is off, all items are rerendered, but none of them wasn't changed practically, which is resulting in flickering appearance of a tooltip:

flickering tooltip

Some investigation

I checked react-window FixedSizeList (which is used for List) render method. And here is some questions which I didn't answer myself yet:

  • Why list itself is rerendered if all props are the same? Seems like this isn't optimized, so the component will be rerendered at any case, won't it? (render method sources)

  • And looking into render method, how props.children are used, List component uses React.createElement to create a new element from passed-in props.children. So I started to think that it's possible to memoize children and came up with this change to return result from function component:

    const memoizedChild = React.useMemo(() => {
        const memo = ({ index, style }: { index: number; style: any }): React.ReactElement => {
            return (
                <div style={style} key={index}>
                    <Tooltip title={t`stop` as string}>
                        <Button>lalla</Button>
                    </Tooltip>
                </div>
            );
        };
        memo.displayName = 'memo';
        return memo;
    }, []);

    return (
        <List height={758} width={625} itemSize={82} itemCount={filteredProfiles.length}>
            {memoizedChild}
        </List>
    );

And it looks like React.createElement doesn't give a damn about memoized children component and therefore React mounts it over and over again. So I stumbled upon this, wondering if it's React.createElement to blame or user code (my code) is written badly.

Questions

  • Is it possible to resolve this tooltip flickering appearance issue with user code changes? What should be changed?

These will be also helpful to have answered:

  • If previous answer is no, then, what changes should be made inside react-window?

  • And this one especial curious, does React.createElement handles properly memoized type argument, according to its definition:

React.createElement(
  type, <- If this one is memoized, I mean
  [props],
  [...children]
)

Solution

  • Is it possible to resolve this tooltip flickering appearance issue with user code changes? What should be changed?

    All is pretty straight. When I was passing a function as a child, it was constantly being created over and over again. So the solution was to move child function definition out of render method like so:

    const MyButton: React.FC<{ style: any; index: number }> = ({ index, style }) => {
        return (
            <div style={style} key={index}>
                <Tooltip title={t`stop` as string}>
                    <Button>lalla</Button>
                </Tooltip>
            </div>
        );
    };
    

    and use it in render method like so:

    <List height={758} width={625} itemSize={82} itemCount={filteredProfiles.length}>
        {MyButton}
    </List>
    

    And this one especial curious, does React.createElement handles properly memoized type argument, according to its definition ...

    createElement doesn't handle memoization, it simply creates a react element. Memoization itself assures that the component will be not created if nothing changed.