react-autosuggest icon indicating copy to clipboard operation
react-autosuggest copied to clipboard

Is there a way to append the suggestions container to the body?

Open gaurav5430 opened this issue 5 years ago • 9 comments

Are you reporting a bug?

I am using the autosuggest within a component which uses overflow-x: hidden, this makes the overflow-y: auto (you can't set it to visible), which leads to a problem where the suggestions box does not overflow out of the parent container even if I make it absolute.

It is similar to the discussion here: https://stackoverflow.com/questions/6421966/css-overflow-x-visible-and-overflow-y-hidden-causing-scrollbar-issue

It is similar to this reported issue: https://github.com/moroshko/react-autosuggest/issues/112

Codepen: https://codepen.io/gaurav5430/pen/YzPVoOe

Steps to reproduce:

  1. Focus on the input field
  2. Type c, and wait for suggestions to appear

Observed behaviour: Suggestions do not appear

Expected behaviour: Suggestions should appear

One solution to the problem is to append the suggestion box to body, since in my case, I can't control the overflow property of the parent.

Is there currently a way to append the suggestions to the body? Also, would any accessibility functionality break if we append it to the body using something like a react portal ?

gaurav5430 avatar Dec 24 '19 05:12 gaurav5430

I was able to resolve this issue in particular by using ReactDOM.createPortal and renderSuggestionsContainer. Something like this:

renderSuggestionsContainer = ({ containerProps, children }) => {
    let toRender = null;
    // we have to handle the show and hide of the suggestionsContainer, unlike suggestions for which display is handled automatically by Autosuggest
    if (this.state.showSuggestions) {
      // this.input is the input ref as received from Autosuggest
      const inputCoords = this.input.getBoundingClientRect();
      const style = {
        position: 'absolute',
        left: inputCoords.left + window.scrollX, // adding scrollX and scrollY to get the coords wrt document instead of viewport
        top: inputCoords.top + 52 + window.scrollY,
        maxHeight: '264px',
        overflow: 'auto',
        zIndex: 4,
        backgroundColor: '#ffffff',
        borderRadius: '4px',
        boxShadow: '0 4px 16px 0 rgba(0,0,0,.15), 0 1px 2px 0 rgba(0,0,0,.07), 0 0 1px 0 rgba(0,0,0,.2)',
        width: inputCoords.width,
      };

      toRender = (
        <div {... containerProps} style={style}>
         Some text:
        {children}
        </div>
      );
      return ReactDOM.createPortal(toRender, document.body);
    }

    return null;
  }

This does seem to retain all the accessibility stuff, keyboard listeners and everything.

Some pain points are:

  1. need to specify the whole css inline, as some part of it needs to be dynamic. This might just be dependent on my setup.
  2. getting the input ref was not as straight forward. I am rendering my own input component, and when i was trying to get the ref to input directly, Autosuggest was giving me errors in selectionOnFocus, as discussed in some other issues as I wasn't passing the ref correctly.

gaurav5430 avatar Dec 24 '19 20:12 gaurav5430

I also want to append suggestions but i am stuck because of event listeners.

ronaklalwaniii avatar Dec 31 '19 06:12 ronaklalwaniii

@ronaklalwaniii can you elaborate?

gaurav5430 avatar Dec 31 '19 06:12 gaurav5430

@gaurav5430 I tried to add fixed suggestion using renderSuggestionsContainer but just like you said keyboard listeners are not working.

ronaklalwaniii avatar Jan 02 '20 06:01 ronaklalwaniii

Hi,

For me listeners are working as expected, accessibility is working as expected. May be there is something else you are doing wrong?

gaurav5430 avatar Jan 02 '20 17:01 gaurav5430

Hi @gaurav5430 Are you still going with this approach? Are there any complications?

SonOfCrypto avatar Mar 09 '20 06:03 SonOfCrypto

Yeah, still working fine with this approach, haven't had any issues till now.

gaurav5430 avatar Mar 17 '20 19:03 gaurav5430

@gaurav5430 Great solution.

Some pointers:

  • by wrapping the container in <div className="react-autosuggest__container"> you don't need to repeat the styling (except for the dynamic parts)
  • I'm checking for containerProps.className.indexOf('react-autosuggest__suggestions-container--open') >= 0 to know the state of the container. Probably not as robust but it works for me.

ivesdebruycker avatar Sep 04 '20 21:09 ivesdebruycker

@gaurav5430 @ivesdebruycker suggestions work well. But also consider adding a window resize event listener so that the container is re-rendered in the correct position on window resizes. Also we only need to add the dynamic props for styling, other props can be handled via style files normally. Below is my implementation, using Material-UI and react hooks.

useEffect(() => {
            // Handler to call on window resize
            // Need to watch for window resize so that the
            // autosuggest container gets redraw in the correct position
            // also adding debounce to avoid too many renders
            const handleResize = () => {
                // Set window width/height to state
                debounce(setWindowSize({
                    width: window.innerWidth,
                    height: window.innerHeight,
                }), 250);
            }

            window.addEventListener('resize', handleResize);

            // Remove event listener on cleanup
            return () => window.removeEventListener("resize", handleResize);
        }, []);

    return (
        <Autosuggest
            id={id}
            suggestions={suggestionsStore.suggestions.slice()}
            onSuggestionsFetchRequested={onSuggestionsFetchRequested}
            onSuggestionsClearRequested={onSuggestionsClearRequested}
            onSuggestionSelected={onSuggestionSelected}
            renderInputComponent={renderInputComponent}
            renderSuggestionsContainer={({ containerProps, children }) => {
                let toRender = null;
                if (containerProps.className.indexOf('react-autosuggest__suggestions-container--open') >= 0) {
                    // inputRed is the input ref as received from Autosuggest
                    const inputCoords = inputRef.getBoundingClientRect(),
                        style = {
                            left: inputCoords.left + window.scrollX, // adding scrollX and scrollY to get the coords wrt document instead of viewport
                            top: inputCoords.top + window.scrollY,
                            width: inputCoords.width,
                        };

                    toRender = (
                        <Paper {...containerProps} square style={style}>
                            { children }
                        </Paper>
                    );
                    return ReactDOM.createPortal(toRender, document.querySelector('main .root'));
                }

                return null;
            }}
            ref={storeInputReference}
            inputProps={inputProps}
            {...props}
        />
    );

rafaeldsousa avatar Jul 28 '21 09:07 rafaeldsousa