javascriptreactjsreact-virtualizedreact-beautiful-dndreact-window

Setup problem encountered by wrapping a React-Beautiful-DND + React-Window virtual list with AutoSizer


When wrapping my virtual list <FixedSizeList> component (react-beautiful-dnd + react-window) with an <AutoSizer> component (react-virtualized-auto-sizer) I'm receiving the following error:

react-beautiful-dnd

A setup problem was encountered.

Invariant failed: provided.innerRef has not been provided with a HTMLElement.

The error doesn't occur if I don't wrap the <FixedSizeList> component with <AutoResizer> and supply hard-coded values instead.

My program implements 2 separate, non-draggable lists that I can drag-and-drop to and from. Becasue the lists aren't draggable it's not a typical "board", but I used React-Beautiful-DND's CodeSandBox for React-Window Basic Board as a guide to make it work just as well.

List.js:

import { Draggable, Droppable } from "react-beautiful-dnd";
import { FixedSizeList } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import ListItem from "./ListItem";
import React, { memo, useCallback } from "react";

const List = ({

        ID,
        data
    }) => {

    const listItemRenderer = useCallback(({ data, index, style }) => {

        const item = (data && data[index]);

        if (!item) {

            return null;
        }

        return (

            <Draggable
                key={item.ID}
                draggableId={item.ID}
                index={index}
            >
                {(provided) =>

                    <ListItem
                        data={item}
                        draggableProvided={provided}
                        virtualStyle={style}
                    />
                }
            </Draggable>
        );
    }, []);

    return (

        <Droppable
            droppableId={ID}
            mode={"virtual"}
            renderClone={(provided, snapshot, rubric) => (

                <ListItem
                    data={data[rubric.source.index]}
                    draggableProvided={provided}
                    isDragging={snapshot.isDragging}
                />
            )}
        >
            {(provided, snapshot) => {

                const dataLength = (data)
                    ? data.length
                    : 0;

                const itemCount = (snapshot.isUsingPlaceholder)
                    ? dataLength + 1
                    : dataLength;

                return (

                    <AutoSizer>  //Error here caused by wrapping <FixedSizeList> with <AutoSizer>
                        {({ width, height }) => (

                            <FixedSizeList
                                width={width}   //AutoSizer supplied value
                                height={height} //AutoSizer supplied value
                                itemSize={100}
                                itemCount={itemCount}
                                itemData={data}
                                outerRef={provided.innerRef}
                            >
                                {listItemRenderer}
                            </FixedSizeList>
                        )}
                    </AutoSizer>
                );
            }}
        </Droppable>
    );
};

export default memo(List);

ListItem.js:

import React, { memo } from "react";

const ListItem = ({

        data,
        draggableProvided,
        virtualStyle
    }) => {

    return (

        <div
            {...draggableProvided.draggableProps}
            {...draggableProvided.dragHandleProps}
            ref={draggableProvided.innerRef}
            style={{

                ...draggableProvided.draggableProps.style,
                ...virtualStyle
            }}

        >
            {data.name}
        </div>
    );
};

export default memo(ListItem);

Regardless of the error, everything seems to still function as it should, but I'd really like to understand the problem before moving forward.


Solution

  • I dug into the AutoSizer component to find the answer.

    The error was logged because the children property of the AutoSizer HOC was not being rendered since the width and height values were 0. This is also why everything still functioned normally, as the width and height state values were eventually updated, but only after the initial render.

    AutoSizer (index.esm.js):

    // Avoid rendering children before the initial measurements have been collected.
    // At best this would just be wasting cycles.
    var bailoutOnChildren = false;
    
    if (!disableHeight) {
    if (height === 0) {
        bailoutOnChildren = true;
    }
    outerStyle.height = 0;
    childParams.height = height;
    }
    
    if (!disableWidth) {
    if (width === 0) {
        bailoutOnChildren = true;
    }
    outerStyle.width = 0;
    childParams.width = width;
    }
    
    return createElement(
    'div',
    {
        className: className,
        ref: this._setRef,
        style: _extends({}, outerStyle, style) },
    !bailoutOnChildren && children(childParams)
    );
    

    Therefore, the solution is to supply defaultWidth and defaultHeight props with non-zero value to ensure that the component renders on mount, albeit with non-automated sizes:

    //...
    
    return (
    
        <AutoSizer
            defaultWidth={1}
            defaultHeight={1}
        >
            {({ width, height }) => (
    
                <FixedSizeList
                    width={width} 
                    height={height}e
                    itemSize={100}
                    itemCount={itemCount}
                    itemData={data}
                    outerRef={provided.innerRef}
                >
                    {listItemRenderer}
                </FixedSizeList>
            )}
        </AutoSizer>
    );