Obiwahn
Obiwahn

Reputation: 3087

React autocomplete in a textarea like in an IDE (e.g. VS Code, Atom)

I try to code an auto completion feature like in the gif in React. So suggestions appear while writing text.

However all packages I could find so far work

a) only in the beginning of the input/textarea (e.g. react-autosuggest)

b) or need a trigger character (like @ or #) to open (e.g. react-textarea-autocomplete)

Do I miss some React limitation? Any hints / packages?

Auto Complete

Upvotes: 7

Views: 10648

Answers (3)

Obiwahn
Obiwahn

Reputation: 3087

We ended up using the fantastic editor Slate.js.

The Mentions example can easily be changed so that the suggestions are triggered by any character (not only '@'). There you go: perfect auto suggest.

Upvotes: 3

sanyangkkun
sanyangkkun

Reputation: 101

I'm actually having the same problem in regards to needing a textarea implementation, but I can help with autocomplete triggering behavior.

We have an implementation of template variables that look like this {{person.name}} which get resolved into whatever the actual value is.

In regards to the autocompletion being triggered only on the first word, you can get around that with a couple modifications to the required functions.

For instance my required functions look like this. (not a completely working example, but all the important bits)


    const templateVars = Object.values(TemplateVarMap);

    const variables = templateVars.map((templateVar) => {
        return {
            name: templateVar,
        };
    });

    //This func, onChange, and onSuggestionSelected/Highlight are the important
    //parts. We essentially grab the full input string, then slice down to our 
    //autocomplete token and do the same for the search so it filters as you type
    const getSuggestions = (value) => {
        const sliceIndex = value
            .trim()
            .toLowerCase()
            .lastIndexOf('{{'); //activate autocomplete token
        const inputValue = value
            .trim()
            .toLowerCase()
            .slice(sliceIndex + 2); //+2 to skip over the {{
        const inputLength = inputValue.length;
        //show every template variable option once '{{' is typed, then filter as 
        //they continue to type
        return inputLength === 0
            ? variables
            : variables.filter(
                (variable) => variable.name.toLowerCase().slice(0, inputValue.length) === inputValue
            );
    };

    const getSuggestionValue = (suggestion) => suggestion.name;

    const renderSuggestion = (suggestion) => <div>{suggestion.name}</div>;

    onSuggestionsFetchRequested = ({ value }) => {
        this.setState({
            suggestions: getSuggestions(value),
        });
    };

    onSuggestionsClearRequested = () => {
        this.setState({
            suggestions: [],
        });
    };


    onChange = (event, { newValue }) => {
        //onChange fires on highlight / selection and tries to wipe
        //the entire input to the suggested variable, so if our value
        //is exactly a template variable, don't wipe it 
        if (templateVars.includes(newValue)) {
            return;
        }
        this.setState({
            value: newValue,
        });
    };

    //These both need to do similar things because one is a click selection
    //and the other is selection using the arrow keys + enter, we are essentially
    //manually going through the input and only putting the variable into the
    //string instead of replacing it outright.
    onSuggestionHighlighted = ({ suggestion }) => {
        if (!suggestion) {
            return;
        }
        const { value } = this.state;
        const sliceIndex = value.lastIndexOf('{{') + 2;
        const currentVal = value.slice(0, sliceIndex);
        const newValue = currentVal.concat(suggestion.name) + '}}';
        this.setState({ value: newValue });
    };

    onSuggestionSelected = (event, { suggestionValue }) => {
        const { value } = this.state;
        const sliceIndex = value.lastIndexOf('{{') + 2;
        const currentVal = value.slice(0, sliceIndex);
        const newValue = currentVal.concat(suggestionValue) + '}}';
        this.setState({ value: newValue });
    };

    const inputProps = {
        value: this.state.value,
        onChange: this.onChange,
    };

    render() {
        return (
            <Autosuggest
                suggestions={this.state.suggestions}
                onSuggestionSelected={this.onSubjectSuggestionSelected}
                onSuggestionHighlighted={this.onSubjectSuggestionHighlighted}
                onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
                onSuggestionsClearRequested={this.onSuggestionsClearRequested}
                getSuggestionValue={getSuggestionValue}
                renderSuggestion={renderSuggestion}
                inputProps={inputProps}
            />
        )
   }

This lets me type something like This is some text with a {{ and have autocomplete pop up, upon choosing a selection it should go to This is some text with a {{person.name}}.

The only problem here is that it requires the final two characters in the input to be {{ (or whatever your token is) for the autocomplete box to come up. I'm still playing with cursor movement and slicing the string around in different ways so if I edit a template thats not at the end the box still pops up.

Hopefully this helps.

Upvotes: 0

eltuza
eltuza

Reputation: 84

You can try react-predictive-text

Upvotes: -2

Related Questions