Ahmet Firat Keler
Ahmet Firat Keler

Reputation: 4040

React-Select, adding copy paste feature to multi select text input

I have a react-select component (which is generated using CreatableSelect), it is a multi select text input which allows users to add keywords as options.

It's working fine but I need a way to allow users to copy some comma separated text and paste it into the component so that each item is going to be added as a single option.

For instance, if the text is "123,456,789", the expected output will be 3 individual options: 123, 456 and 789, respectively.

Here's my component

import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';

const MultiSelectTextInput = (props) => {
    const components = {
        DropdownIndicator: null,
    };

    interface Option {
        readonly label: string;
        readonly value: string;
    }

    const createOption = (label: string) => ({
        label,
        value: label,
    });

    const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
        console.group('Value Changed');
        console.log(value);
        console.log(`action: ${actionMeta.action}`);
        console.groupEnd();
        props.setValue(value);
    };

    const handleInputChange = (inputValue: string) => {
        props.setInputValue(inputValue);
    };

    const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
        if (!props.inputValue) return;

        switch (event.key) {
            case 'Enter':
            case 'Tab':
                if (props.value.map(v => v.label).includes(props.inputValue)) {
                    console.log('Value Already Exists!')
                    props.setInputValue('');
                }
                else {
                    console.group('Value Added');
                    console.log(props.value);
                    console.groupEnd();
                    props.setInputValue('');
                    props.setValue([...props.value, createOption(props.inputValue)])
                }

                event.preventDefault();
        }
    };

    return (
        <CreatableSelect
            id={props.id}
            instanceId={props.id}
            className="w-100"
            components={components}
            inputValue={props.inputValue}
            isClearable
            isMulti
            menuIsOpen={false}
            onChange={handleChange}
            onInputChange={handleInputChange}
            onKeyDown={handleKeyDown}
            placeholder={props.placeholder}
            value={props.value}
        />
    );
};

export default MultiSelectTextInput;

I call this component from a page (of a next.js project) like below. The component modifies the state of the page in which it is called.

...
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
...

const NcciLite = () => {
    const [value, setValue] = useState<any>([]);
    const [inputValue, setInputValue] = useState<any>('');
    ...
    return (
        <React.Fragment>
            <Container fluid={true}>
                <Breadcrumbs title="Tools" breadcrumbItem="NCCI Lite" />
                <Row>
                    <Col>
                        <Card>
                            <CardBody>
                                <CardTitle className="mb-4 fw-light">...</CardTitle>
                                <Form onSubmit={event => event.preventDefault()}>
                                   ...
                                    <Row className="mb-4">
                                        <Col>
                                            <div className="d-inline-flex col-md-9">
                                                <MultiSelectTextInput
                                                    id="codes"
                                                    value={value}
                                                    setValue={setValue}
                                                    inputValue={inputValue}
                                                    setInputValue={setInputValue}
                                                    placeholder="Type Next Code(s) or Paste Here"
                                                />
                                            </div>
                                        </Col>
                                    </Row>
                                   ...
                                </Form>
                            </CardBody>
                        </Card>
                    </Col>
                </Row>
            </Container>
        </React.Fragment>
};

export default NcciLite;

Any help would be appreciated

Upvotes: 2

Views: 3040

Answers (1)

Ahmet Firat Keler
Ahmet Firat Keler

Reputation: 4040

Allright, I will answer my own question. I thought it would be difficult but it was actually not. Just a few lines of javascript code.

Modified component

import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';

const MultiSelectTextInput = (props) => {
    const components = {
        DropdownIndicator: null,
    };

    interface Option {
        readonly label: string;
        readonly value: string;
    }

    const createOption = (label: string) => ({
        label,
        value: label,
    });

    const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
        // console.group('Value Changed');
        // console.log(value);
        // console.log(`action: ${actionMeta.action}`);
        // console.groupEnd();
        props.setValue(value);
    };

    const handleInputChange = (inputValue: string) => {
        props.setInputValue(inputValue);
    };

    const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
        if (!props.inputValue) return;

        switch (event.key) {
            case 'Enter':
            case 'Tab':
                if (props.value.map(v => v.label.trim()).includes(props.inputValue.trim())) {
                    // console.log('Value Already Exists!')
                    props.setInputValue('');
                }
                else {
                    // console.group('Value Added');
                    // console.log(props.value);
                    // console.groupEnd();
                    if (!props.customizedSetter && props.inputValue.trim().indexOf(',') > -1) {
                        props.setInputValue('');

                        const values = props.inputValue.trim().split(',').filter(iv => !props.value.map(v => v.label.trim()).includes(iv.trim()));

                        for (let i = 0; i < values.length; i++) {
                            props.setValue((oldValue) =>[...oldValue, createOption(values[i].trim())]);
                        }
                    }
                    else {
                        props.setInputValue('');
                        props.setValue([...props.value, createOption(props.inputValue.trim())]);
                    }
                }

                event.preventDefault();
        }
    };

    return (
        <CreatableSelect
            id={props.id}
            instanceId={props.id}
            className="cs w-100"
            components={components}
            inputValue={props.inputValue}
            isClearable
            isMulti
            menuIsOpen={false}
            onChange={handleChange}
            onInputChange={handleInputChange}
            onKeyDown={handleKeyDown}
            placeholder={props.placeholder}
            value={props.value}
        />
    );
};

export default MultiSelectTextInput;


Upvotes: 4

Related Questions