Downshift: Clicked selection not closing dropdown

Created on 13 Oct 2017  路  1Comment  路  Source: downshift-js/downshift

  • downshift version: 1.11.1
  • node version: 6.9.5
  • npm (or yarn) version: yarn v1.1.0

What you did:
Seemingly nothing different than the provided examples. I attempt to click to choose my selection, but the menu stays open with that selection filtered and selected. Not controlling value or isOpen. Note that in the log it shows type "__autocomplete_unkown__" instead of the normal mouseup event.

Relevant code or config

// @flow

import React, {Component} from 'react';
import Downshift from 'downshift';
import classnames from 'classnames';

import ArrowDown from 'dibs-vg/dist/react/arrow-down';
import ErrorMessage from '../Common/ErrorMessage';

type SearchableSelectTheme = {
    animatedPlaceholderStart: string,
    animatedPlaceholderEnd: string,
    arrowDown: string,
    arrowUp: string,
    dropdown: string,
    dropdownExpanded: string,
    heightDefault: string,
    heightSmall: string,
    heightMedium: string,
    heightLarge: string,
    disabled: string,
    error: string,
    fieldContainer: string,
    icon: string,
    iconContainer: string,
    inputContainer: string,
    inputFocus: string,
    inputErrorMessage: string,
    inputPlaceholderContainer: string,
    label: string,
    option: string,
    optionHighlight: string,
    optionSelected: string,
    placeholder: string,
    withAnimatedPlaceholder: string
};

type SelectOption = { value: string, label: string };

type SearchableSelectProps<T: SelectOption> = {
    autoComplete?: string,
    autoFocus?: boolean,
    dataTn: string,
    disabled?: boolean,
    errorDataTn?: string,
    errorMessage?: string,
    hasAnimatedPlaceholder?: boolean,
    isFocused?: boolean,
    label?: string,
    options: Array<T>,
    onChange: ?(
        option:
            | ?({ name: string } & $Shape<T>)
            | ?$ReadOnlyArray<{ name?: string } & $Shape<T>>
    ) => mixed,
    placeholder?: string,
    size?: 'small' | 'medium' | 'large',
    theme?: SearchableSelectTheme,
    value: ?(number | string)
};

type SearchableSelectState = {
    isFocused?: boolean,
    value: ?(number | string)
};

class SearchableSelect extends Component<SearchableSelectProps<SelectOption>, SearchableSelectState> {
    static defaultProps = {
        autoComplete: ""
    };

    constructor(props: SearchableSelectProps<SelectOption>) {
        super(props);

        this.onBlur = this.onBlur.bind(this);
        this.onDSStateChange = this.onDSStateChange.bind(this);
        this.onFocus = this.onFocus.bind(this);

        this.state = {
            isFocused: props.autoFocus || !!props.isFocused,
            value: props.value || ''
        };
    }

    componentWillReceiveProps(nextProps: SearchableSelectProps<SelectOption>) {
        let willUpdate = false;
        let value = this.state.value;
        let isFocused = this.state.isFocused;

        const {hasAnimatedPlaceholder, placeholder} = nextProps;
        const {placeholderAnimatedUp} = this.state;

        /* istanbul ignore else */
        if (typeof nextProps.value !== 'undefined' && !(value === nextProps.value === this.props.value)) {
            willUpdate = true;
            value = nextProps.value;
        }

        /* istanbul ignore else */
        if (typeof nextProps.isFocused !== 'undefined') {
            willUpdate = true;

            isFocused = nextProps.isFocused;
        }

        /* istanbul ignore else */
        if (willUpdate) {
            this.setState({
                value,
                isFocused
            });

            // if (willUpdate) {
            //     if (hasAnimatedPlaceholder && !!placeholder) {
            //         if (placeholderAnimatedUp && !value) {
            //             this.setState({placeholderAnimatedUp: false, isFocused: true}, () => {
            //                 setTimeout(() => {
            //                     this.setState({value, isFocused});
            //                 }, 100);
            //             });
            //         } else if (!placeholderAnimatedUp && !!value) {
            //             this.setState({value, isFocused}, () => {
            //                 setTimeout(() => {

            //                     this.setState({placeholderAnimatedUp: true});
            //                 }, 10);
            //             });
            //         } else {
            //             this.setState({ value, isFocused });
            //         }
            //     } else {
            //         this.setState({ value, isFocused });
            //     }
            // }
        }
    }


    itemToString = (item: SelectOption) => {
        if (item && item.label) {
            return item.label;
        }
        return "";
    }

    onBlur = () => {
        this.setState({
            isFocused: false
        });
    }

    onChange = (option: SelectOption) => {
        console.log('changing to: ', option)
        if (this.props.onChange) {
            this.props.onChange(option)
        }

        // setTimeout(() => {
        //     this.setState({
        //         isFocused: false
        //     });
        // }, 10);
    }

    onDSStateChange = (newState: any) => {
        const {type} = newState;
        console.log('new state: ', newState)

        if (["__autocomplete_blur_input__", "__autocomplete_mouseup__"].includes(type)) {
            console.log('blurrrr')
            this.onBlur();
        }
    }

    // onOuterClick = (stuff) => {
    //     console.log("stuff: ", stuff)
    //     this.setState({
    //         isOpen: false,
    //         isFocused: false
    //     });
    // }

    onFocus = () => {
        this.setState({
            isFocused: true
        });
    }

    // openSelect = () => {
    //     this.setState({
    //         isOpen: true,
    //         isFocused: true
    //     });
    // }

    renderPlaceholder = () => {
        const { hasAnimatedPlaceholder, placeholder, theme } = this.props;
        const {isFocused, value, placeholderAnimatedUp} = this.state;

        const shouldRender = placeholder && hasAnimatedPlaceholder && (isFocused || !!value);

        if (!theme || !shouldRender) {
            return null;
        }
        const placeholderClasses = classnames(theme.animatedPlaceholderStart, {
            [theme.animatedPlaceholderEnd]: placeholderAnimatedUp || !!value
        });

        return <span className={placeholderClasses}>{placeholder}</span>;
    };

    render() {
        const {
            dataTn,
            disabled,
            errorDataTn,
            errorMessage,
            hasAnimatedPlaceholder,
            label,
            options,
            placeholder,
            size,
            theme
        } = this.props;

        const {isFocused, value} = this.state;

        if (!theme) {
            return null;
        }

        const hasErrorText = !!errorMessage;
        const errorDataTnString = errorDataTn ? errorDataTn : `${dataTn}-error`;

        const inputContainerClasses = classnames(theme.inputContainer, {
            [theme.heightDefault]: !size,
            [theme.heightSmall]: size === 'small',
            [theme.heightMedium]: size === 'medium',
            [theme.heightLarge]: size === 'large',
            [theme.error]: hasErrorText,
            [theme.inputFocus]: !hasErrorText && isFocused,
            [theme.disabled]: disabled,
            [theme.withAnimatedPlaceholder]: hasAnimatedPlaceholder && (!!value || isFocused)
        });

        const hasValue = !!value;
        const inputClasses = classnames(theme.input, {
            [theme.disabled]: disabled
        });

        const nativePlaceholder = hasAnimatedPlaceholder && (isFocused || !!value) ? "" : placeholder;
        return (
            <Downshift
                itemToString={this.itemToString}
                onStateChange={this.onDSStateChange}
                onChange={this.onChange}
            >
            {({
                getInputProps,
                getItemProps,
                inputValue,
                highlightedIndex,
                selectedItem,
                isOpen,
                closeMenu,
                openMenu
            }) => {
                const arrowClasses = classnames(theme.icon, {
                    [theme.arrowUp]: isOpen
                });
                return (
                    <div className={theme.fieldContainer}>
                        {label && <div className={theme.label}>{label}</div>}
                        <div className={inputContainerClasses} onClick={disabled ? () => {} : openMenu}>
                            <div className={theme.inputPlaceholderContainer}>
                                <input
                                    autoFocus={isFocused}
                                    {...getInputProps({
                                        placeholder: nativePlaceholder,
                                    })}
                                    className={inputClasses}
                                    disabled={disabled}
                                    onFocus={this.onFocus}
                                />
                                {this.renderPlaceholder()}
                            </div>
                            <div className={theme.iconContainer}>
                                <ArrowDown className={arrowClasses}/>
                            </div>

                            <div className={classnames(theme.dropdown, {
                                [theme.dropdownExpanded]: isOpen
                            })}>
                                {options
                                  .filter(
                                    i =>
                                      !inputValue ||
                                      i.label.toLowerCase().includes(inputValue.toLowerCase()),
                                  )
                                  .map((item, index) => (
                                    <div
                                        {...getItemProps({item})}
                                        key={`${item.value}-${index}`}
                                        className={classnames(theme.option, {
                                            [theme.optionHighlight]: highlightedIndex === index,
                                            [theme.optionSelected]: selectedItem && selectedItem.value === item.value
                                        })}
                                    >
                                      {item.label}
                                    </div>
                                  ))}
                            </div>
                        </div>

                        <ErrorMessage
                            message={errorMessage}
                            dataTn={errorDataTnString}
                            className={theme.inputErrorMessage}
                        />
                    </div>
                );
            }}
            </Downshift>
        );
    }
};

SearchableSelect.displayName = 'SearchableSelect';

export default SearchableSelect;

http://g.recordit.co/IQcUgnMMRL.gif

What happened:

Reproduction repository:

Problem description:

Suggested solution:

question

Most helpful comment

There's a lot of code to sift through here. It would help a lot if you provided a reproduction repository or a codesandbox. In addition, try removing any unrelated code so that you have just the bare minimum needed to reproduce the issue.

>All comments

There's a lot of code to sift through here. It would help a lot if you provided a reproduction repository or a codesandbox. In addition, try removing any unrelated code so that you have just the bare minimum needed to reproduce the issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Vincent-Alibert picture Vincent-Alibert  路  4Comments

preraksola picture preraksola  路  4Comments

kohgpat picture kohgpat  路  3Comments

gsimone picture gsimone  路  3Comments

yuripramos picture yuripramos  路  4Comments