focusInputOnSuggestionClick throwing error when creating input ref
Came across an error with focusInputOnSuggestionClick when trying to automatically focus the input field.
https://codepen.io/marclemagne/pen/RjRdvr
- Create an input using
renderInputComponent - Create a
refto the input on the class (e.g.,autosuggestInput) - Type
c, and wait for suggestions to appear - Observe in the console the following error:
autosuggest.js:698 Uncaught TypeError: Cannot read property 'focus' of undefined
at Object.onSuggestionClick [as onClick] (autosuggest.js:698)
at Item._this.onClick (autosuggest.js:2941)
at Object.ReactErrorUtils.invokeGuardedCallback (react-dom.js:9017)
at executeDispatch (react-dom.js:3006)
at Object.executeDispatchesInOrder (react-dom.js:3029)
at executeDispatchesAndRelease (react-dom.js:2431)
at executeDispatchesAndReleaseTopLevel (react-dom.js:2442)
at Array.forEach (<anonymous>)
at forEachAccumulated (react-dom.js:15423)
at Object.processEventQueue (react-dom.js:2645)
Questions I have:
- Is there a problem with how we're trying autofocus the input field?
- Is there a more canonical way to set focus when the component loads?
Thanks!
Are you using renderInputComponent only to autofocus the input?
If yes, check out this example that doesn't use renderInputComponent.
If you really want to customize the input, here is how you should access it: Codepen
@moroshko I'm getting the same error often. I need to use Material-UI Next TextField. It's a functional stateless component, so I had to wrap it, so refs can be attached to it. But it still throws the above error.
import TextField from 'material-ui/TextField'
// Wrap function or functional stateless component as react class component.
// Useful e.g. if you need to reference the component in React 16.
class Componentize extends React.Component<any, null> {
public render() {
const { Component, component, ...props } = this.props
return Component ? <Component {...props} /> : component(props)
}
}
const renderInput = props => <Componentize Component={TextField} {...props} />
It's at https://github.com/moroshko/react-autosuggest/blob/master/src/Autosuggest.js#L382
Thanks, @moroshko! Sorry I'm just returning to this now.
Some follow-up with my experience and how (for better or worse) I was able to get things working.
Initially, I was working with an input styled-component and my goal was to have it focus automatically.
public input: HTMLInputElement;
public componentDidMount() {
this.input.focus();
}
public storeInputReference = (autosuggest) => {
if (autosuggest != null) {
this.input = autosuggest.input;
}
}
public renderInputComponent = (inputProps) => {
return (
<TextInput {...inputProps} innerRef={inputProps.ref} ref={null} />
);
}
And in the Autosuggest component add:
renderInputComponent={this.renderInputComponent}
ref={this.storeInputReference}
Note the innerRef in the renderInputComponent method. This is what allows you to access the input element within a styled-component.
I am (still) not actually 100% clear on what the ref={null} is for, but everything was working as I needed. I saw it in another issue thread.
Eventually I extended our TextInput component to focus or select itself which added a complication with my usage of AutoSuggest.
I upgraded the styled-component to be a React class component and had it create its own local ref in order to focus/select.
What I was doing above no longer worked. The TextInput would focus, as expected, and AutoSuggest would function nicely after removing the focus-related code in the AutoSuggest parent class—but I was back to getting the JavaScript TypeError.
So here's what I am doing now:
I removed ref={this.storeInputReference} from the Autosuggest component. I removed the componentDidMount lifecycle method, as well.
public input: HTMLInputElement;
public renderInputComponent = (inputProps) => {
return (
<TextInput
focused
{...inputProps}
getRef={(input) => this.input = input}
ref={() => inputProps.ref(this.input)}
/>
);
}
In our TextInput component I added a new prop called getRef which returns its local version of this.input in its componentDidMount lifecycle method. In TextInput this.input ref is set in the standard way (ref={input => this.input = input}).
Back to where I'm using AutoSuggest, I noticed that inputProps.ref was returning a function no matter if ref={...} was a prop on Autosuggest or not. So I am using the getRef prop from my TextInput component to get the TextInput input ref and setting that reference locally where I'm using AutoSuggest. Then in the ref prop being passed into TextInput, I am calling that inputProps.ref function to (ostensibly) let Autosuggest know about the reference to the input.
No more error and everything is working correctly.
It's not exactly elegant but it seems to be working. I am sure there has got to be a cleaner, simpler way to achieve this. At the end of the day, though, it seems that AutoSuggest needs a reference to the input or it's going to throw an error.
I am wondering if AutoSuggest should do an existence check on this.input before trying to focus using it.
I hope this proves to be somewhat helpful for anybody else having similar issues.
@marclemagne Thanks so much for that last comment. There's like a dozen issues around refs, but this one finally solved it for me. Debugging a custom input that is a styled-component, not necessarily needing the ref as you do, ref={null} was the key.
Unbelievable. This error was really not clear to me. But after trying so many things, the solution ended being as simple as moving the ref that was being assigned from the renderInputComponent function return to the actual Autosuggest component.
...
const renderInputComponent = inputCompProps => (
<input
{...inputCompProps}
// ref={inputRef} // NOT HERE
styleName="input-field"
/>
);
return (
<div>
<Autosuggest
id={id}
alwaysRenderSuggestions={alwaysRenderSuggestions}
focusInputOnSuggestionClick={false}
getSuggestionValue={getSuggestionValue}
highlightFirstSuggestion
inputProps={inputProps}
onSuggestionsClearRequested={handleSuggestionsClearRequested}
onSuggestionSelected={handleSuggestionSelected}
onSuggestionsFetchRequested={handleSuggestionsFetchRequested}
ref={inputRef} // GOES HERE
renderInputComponent={renderInputComponent}
renderSuggestion={renderSuggestion}
suggestions={suggestions}
/>
</div>
);
...