masonic icon indicating copy to clipboard operation
masonic copied to clipboard

fix(use-masonry): return children sorted by index

Open Obi-Dann opened this issue 2 years ago • 1 comments

Changes from the PR add logic to sort children rendered in use-masonry by their index. This should help to maintain the tabbing order as the items will be rendered in a stable order - they often had a pretty random non-stable order before the change.

I considered doing it on our side in user code, but it's a bit hard to mutate use-masonry output - the implementation would be fragile to future updates in masonic that could potentially change the rendering logic

example implementation fixing the issue in user code
function AwesomeMasonryComponent(props) {
    // ...
    return useMasonry({
        ...props
        as: SortedMasonryItems,
    });
}

const SortedMasonryItems = ({ children, ...props }: React.ComponentProps<'div'>) => {
    const sortedChildren = React.Children.toArray(children).sort((a, b) => {
        const indexA = getIndex(a);
        const indexB = getIndex(b);
        if (typeof indexA !== 'number' || typeof indexB !== 'number') {
            return 0;
        }

        return indexA - indexB;
    });

    return <div {...props}>{sortedChildren}</div>;
};

function getIndex(
    element: React.ReactChild | React.ReactFragment | React.ReactPortal,
): number | undefined {
    // masonic renders children like that:
    // <ItemComponent
    //     key={key}
    //     ref={setItemRef(index)}
    //     role={itemRole}
    //     style={
    //         typeof itemStyle === 'object'
    //             ? Object.assign({}, phaseOneStyle, itemStyle)
    //             : phaseOneStyle
    //     }
    // >
    //     {createRenderElement(RenderComponent, index, data, columnWidth)}
    // </ItemComponent>;
    // so, we need to access the child of the child to get the index props and use it for sorting
    if (!React.isValidElement(element)) {
        return undefined;
    }

    const child = React.Children.only(
        (element as React.ReactElement<{ children?: React.ReactNode }>).props.children,
    );

    if (React.isValidElement(child)) {
        const index = child.props.index;
        if (typeof index === 'number') {
            return index;
        }
    }

    return undefined;
}

closes #74

Obi-Dann avatar Mar 31 '22 21:03 Obi-Dann

Thanks for submitting! I will give it a loot this weekend

jaredLunde avatar Apr 01 '22 14:04 jaredLunde