javascriptreactjsreact-select

How to implement an option that inverts all selection in react-select?


I have a Select from react-select that's multi option, I want an option placed on top op the others that invert selected options to unselected and vice versa.

And when this 'Invert selection' option is selected don't show in grey into selected options preview.

I know that there's a second parameter to onChange prop that means the action, where I can see which option were selected or unelected, but I don't know how to properly manage the options.

How my Select component looks like:

selectui

Code:

<Select
    placeholder="Selecionar..."
    defaultValue={issuesTypes.results.map(
        (iT) =>
            selectedCategoryOptions.includes(iT.id) && {
                value: iT.id,
                label: iT.title,
            }
    )}
    isMulti
    closeMenuOnSelect={false}
    hideSelectedOptions={false}
    onChange={(options, a) => {
        if (Array.isArray(options)) {
            setSelectedCategoryOptions(options.map((opt) => opt.value));
        }
    }}
    options={(() => {
        let aux = [];
        issuesTypes.results.map((iT) => {
            if (iT.isActive) {
                aux.push({
                    value: iT.id,
                    label: iT.title,
                });
            }
        });
        return aux;
    })()}
    components={{
        Option: DefaultInputOption,
    }}
/>;

const DefaultInputOption = ({
    getStyles,
    Icon,
    isDisabled,
    isFocused,
    isSelected,
    children,
    innerProps,
    ...rest
}) => {
    const [isActive, setIsActive] = useState(false);
    const onMouseDown = () => setIsActive(true);
    const onMouseUp = () => setIsActive(false);
    const onMouseLeave = () => setIsActive(false);

    // styles
    let bg = "transparent";
    if (isFocused) bg = "#eee";
    if (isActive) bg = "#B2D4FF";

    const style = {
        alignItems: "center",
        backgroundColor: bg,
        color: "inherit",
        display: "flex ",
    };

    // prop assignment
    const props = {
        ...innerProps,
        onMouseDown,
        onMouseUp,
        onMouseLeave,
        style,
    };

    return (
        <components.Option
            {...rest}
            isDisabled={isDisabled}
            isFocused={isFocused}
            isSelected={isSelected}
            getStyles={getStyles}
            innerProps={props}
        >
            <input type="checkbox" checked={isSelected} onChange={() => {}} />
            {children}
        </components.Option>
    );
};

Solution

  • Found the solution. Inside components prop you can overwrite some components, just like I did with Option in the above question, so I created a MenuList wich is the dropdown menu and passed to components:

    components={{
         Option: DefaultInputOption,
         MenuList: MenuList,
    }}
    

    MenuList:

    const MenuList = (props) => {
        return (
            <components.MenuList {...props}>
                <div
                    style={{
                        padding: "8px 12px",
                        cursor: "pointer",
                        borderBottom: "1px solid rgb(220, 220, 220)",
                    }}
                    onClick={async (e) => {
                        for (let i = 0; i < props.children.length; i++) {
                            let child = props.children[i];
                            await child.props.selectOption(child.props.data);
                        }
                    }}
                >
                    Invert selection
                </div>
                {props.children}
            </components.MenuList>
        );
    };
    

    As you can see it has a div with inner text "Invert Selection" and the magic is in the onClick function, calling selectOption will trigger the UI selection on inputs inside DefaultInputOption and the onChange inside <Select />.