moroshko / react-autosuggest

WAI-ARIA compliant React autosuggest component
http://react-autosuggest.js.org
MIT License
5.97k stars 586 forks source link

Using tether with react-autosuggest #258

Open Aaronius opened 8 years ago

Aaronius commented 8 years ago

I'd like to use tether for rendering the suggestions container but I can't figure out how I would do so. Has anyone worked this out?

gotdibbs commented 7 years ago

This is how I got this working. Note that I removed any code that was irrelevant to getting tether working, so this example will not run by itself.

I borrowed inspiration from here. Certainly not the prettiest solution, but it appears to cover my use cases.

import React from 'react';
import ReactDOM from 'react-dom';

import Tether from 'tether';
import Autosuggest from 'react-autosuggest';

class AutoComplete extends React.Component {

    componentDidMount() {
        this.update();
    }

    componentDidUpdate() {
        this.update();
    }

    componentWillUnmount() {
        this.destroy();
    }

    update() {
        if (!this.listComponent || !this.textInput) return;

        if (!this.tether) {
            this.tether = new Tether({
                attachment: 'top left',
                targetAttachment: 'bottom left',
                element: ReactDOM.findDOMNode(this.listComponent),
                target: ReactDOM.findDOMNode(this.textInput),
            });
        }
        this.tether.position();
    }

    destroy() {
        this.tether.destroy();
    }

    renderInputComponent = inputProps => {
        return (
            <input {...inputProps} ref={(i) => this.textInput = i} />
        );
    }

    renderSuggestionsContainer = ({containerProps, children}) => {
        return (
            <div { ...containerProps}
                    style={ { position: 'static', minWidth: '15rem', 'maxHeight': '10rem' } }
                    ref={ c => this.listComponent = c; containerProps.ref(this.listComponent); }>
                {children}
            </div>
        );
    }

    render() {
        return (
            <Autosuggest
             renderInputComponent={this.renderInputComponent}
             renderSuggestionsContainer={this.renderSuggestionsContainer} />
        );
    }
}

export default AutoComplete;
rosskevin commented 7 years ago

@gotdibbs did you consider using the https://github.com/souporserious/react-tether and decide not to?

gotdibbs commented 7 years ago

@rosskevin That I did. I had issues getting it to work with the way that solution is architected. I believe it had something to do with the whole first child/second child mapping to the target and element parameters.

rosskevin commented 7 years ago

I've successfully been able to use react-popper (popper.js has replaced tether in many projects including bootstrap) with approximately the following:

import {Manager, Target, Popper} from 'react-popper'
import Portal from 'react-travel'

  renderInputTarget = (renderInputProps: RenderInputProps) => {
    return <Target>{this.props.renderInput(renderInputProps)}</Target>
  }

  renderSuggestionsContainerPopper = (options: RenderSuggestionsContainerOptions) => {
    return (
      <Portal>
        <Popper placement='bottom-start'>
          {this.props.renderSuggestionsContainer(options)}
        </Popper>
      </Portal>
    )
  }

  render () {
    const {
      classes,
      getSuggestionValue,
      shouldRenderSuggestions,
      value: valueProp,
      getSuggestions, onBlurMatchSuggestion, // eslint-disable-line no-unused-vars
      onlyChangeOnSelection, onSuggestionSelected, renderInput, renderSuggestionsContainer, renderSuggestion, // eslint-disable-line no-unused-vars
      ...other
    } = this.props

    return (
      <Manager>
        <ReactAutoSuggest
          getSuggestionValue={getSuggestionValue}
          inputProps={{
            ...other,
            classes: classes,
            onBlur: this.handleBlur,
            onChange: this.handleInputChange,
            value: inputValue || ''
          }}
          onSuggestionSelected={this.handleSuggestionSelected}
          onSuggestionsFetchRequested={this.handleSuggestionsFetchRequested}
          onSuggestionsClearRequested={this.handleSuggestionsClearRequested}
          renderInputComponent={this.renderInputTarget}
          renderSuggestion={this.renderSuggestion}
          renderSuggestionsContainer={this.renderSuggestionsContainerPopper}
          shouldRenderSuggestions={shouldRenderSuggestions}
          suggestions={this.state.suggestions}
          theme={{
            container: classes.container,
            suggestionsContainerOpen: classes.suggestionsContainerOpen,
            suggestionsList: classes.suggestionsList,
            suggestion: classes.suggestion
          }}
        />
      </Manager>
    )
  }  

It works well and very minimal setup, still pluggable with provided render* methods. It also uses a Portal so there are no issues with ancestor elements hiding suggestions by specifying overflow.

gotdibbs commented 7 years ago

@rosskevin Sadly we're already too close to :shipit: mode to change now for my use case, but thanks for the tip! Def looks better.

be-next-hotdog commented 7 years ago

@rosskevin it'd better put your complete example (especially including how to wrap the target dom by PopperJS) Sandbox no ? :)

rosskevin commented 7 years ago

@be-next-hotdog feel free to do so. I pasted psuedocode from a custom internal codebase as a help for others. Unfortunately I have limited time, so perfect examples for everyone are unlikely to appear.

be-next-hotdog commented 7 years ago

@rosskevin Yepp, besides of this issue, I remarked that Popover ruins totally the portal while putting all these components inside of a Popover

ghost commented 5 years ago

@be-next-hotdog feel free to do so. I pasted psuedocode from a custom internal codebase as a help for others. Unfortunately I have limited time, so perfect examples for everyone are unlikely to appear.

@rosskevin So have you found a solution to scroll in portal/popper ?