Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Cannot read properties of undefined (reading 'focus') #839

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-autosuggest",
"version": "10.1.0",
"name": "@nfort/react-autosuggest",
"version": "11.0.1",
"description": "WAI-ARIA compliant React autosuggest component",
"main": "dist/index.js",
"repository": {
Expand Down
44 changes: 25 additions & 19 deletions src/Autosuggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { defaultTheme, mapToAutowhateverTheme } from './theme';

const alwaysTrue = () => true;
const defaultShouldRenderSuggestions = (value) => value.trim().length > 0;
const defaultRenderSuggestionsContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
const defaultRenderSuggestionsContainer = ({
containerProps: { innerRef, ...otherContainerProps },
children,
}) => (
<div {...otherContainerProps} ref={innerRef}>
{children}
</div>
);

const REASON_SUGGESTIONS_REVEALED = 'suggestions-revealed';
Expand Down Expand Up @@ -127,14 +132,13 @@ export default class Autosuggest extends Component {
this.justMouseEntered = false;

this.pressedSuggestion = null;

this.autowhatever = React.createRef();
}

componentDidMount() {
document.addEventListener('mousedown', this.onDocumentMouseDown);
document.addEventListener('mouseup', this.onDocumentMouseUp);

this.input = this.autowhatever.input;
this.suggestionsContainer = this.autowhatever.itemsContainer;
}

// eslint-disable-next-line camelcase, react/sort-comp
Expand Down Expand Up @@ -205,6 +209,14 @@ export default class Autosuggest extends Component {
document.removeEventListener('mouseup', this.onDocumentMouseUp);
}

getInput() {
return this.autowhatever.current.input;
}

getSuggestionsContainer() {
return this.autowhatever.current.itemsContainer;
}

updateHighlightedSuggestion(sectionIndex, suggestionIndex, prevValue) {
this.setState((state) => {
let { valueBeforeUpDown } = state;
Expand Down Expand Up @@ -321,7 +333,7 @@ export default class Autosuggest extends Component {
return;
}

if (node === this.suggestionsContainer) {
if (node === this.getSuggestionsContainer()) {
// Something else inside suggestions container was clicked
this.justClickedOnSuggestionsContainer = true;
return;
Expand Down Expand Up @@ -364,12 +376,6 @@ export default class Autosuggest extends Component {
return suggestions.length > 0 && shouldRenderSuggestions(value, reason);
}

storeAutowhateverRef = (autowhatever) => {
if (autowhatever !== null) {
this.autowhatever = autowhatever;
}
};

onSuggestionMouseEnter = (event, { sectionIndex, itemIndex }) => {
this.updateHighlightedSuggestion(sectionIndex, itemIndex);

Expand All @@ -390,7 +396,7 @@ export default class Autosuggest extends Component {

onDocumentMouseUp = () => {
if (this.pressedSuggestion && !this.justSelectedSuggestion) {
this.input.focus();
this.getInput().focus();
}
this.pressedSuggestion = null;
};
Expand Down Expand Up @@ -463,7 +469,7 @@ export default class Autosuggest extends Component {
}

if (focusInputOnSuggestionClick === true) {
this.input.focus();
this.getInput().focus();
} else {
this.onBlur();
}
Expand Down Expand Up @@ -511,7 +517,7 @@ export default class Autosuggest extends Component {
onSuggestionTouchMove = () => {
this.justSelectedSuggestion = false;
this.pressedSuggestion = null;
this.input.focus();
this.getInput().focus();
};

itemProps = ({ sectionIndex, itemIndex }) => {
Expand Down Expand Up @@ -610,7 +616,7 @@ export default class Autosuggest extends Component {
},
onBlur: (event) => {
if (this.justClickedOnSuggestionsContainer) {
this.input.focus();
this.getInput().focus();
return;
}

Expand All @@ -630,8 +636,8 @@ export default class Autosuggest extends Component {

this.maybeCallOnChange(event, value, 'type');

if (this.suggestionsContainer) {
this.suggestionsContainer.scrollTop = 0;
if (this.getSuggestionsContainer()) {
this.getSuggestionsContainer().scrollTop = 0;
}

this.setState({
Expand Down Expand Up @@ -814,7 +820,7 @@ export default class Autosuggest extends Component {
itemProps={this.itemProps}
theme={mapToAutowhateverTheme(theme)}
id={id}
ref={this.storeAutowhateverRef}
ref={this.autowhatever}
/>
);
}
Expand Down
17 changes: 12 additions & 5 deletions src/Autowhatever.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ import SectionTitle from './SectionTitle';
import ItemList from './ItemList';

const emptyObject = {};
const defaultRenderInputComponent = (props) => <input {...props} />;
const defaultRenderItemsContainer = ({ containerProps, children }) => (
<div {...containerProps}>{children}</div>
const defaultRenderInputComponent = ({ innerRef, ...otherContainerProps }) => (
<input {...otherContainerProps} ref={innerRef} />
);
const defaultRenderItemsContainer = ({
containerProps: { innerRef, ...otherContainerProps },
children,
}) => (
<div {...otherContainerProps} ref={innerRef}>
{children}
</div>
);
const defaultTheme = {
container: 'react-autowhatever__container',
Expand Down Expand Up @@ -414,7 +421,7 @@ export default class Autowhatever extends Component {
onFocus: this.onFocus,
onBlur: this.onBlur,
onKeyDown: this.props.inputProps.onKeyDown && this.onKeyDown,
ref: this.storeInputReference,
innerRef: this.storeInputReference,
});
const itemsContainer = renderItemsContainer({
containerProps: {
Expand All @@ -425,7 +432,7 @@ export default class Autowhatever extends Component {
'itemsContainer',
isOpen && 'itemsContainerOpen'
),
ref: this.storeItemsContainerReference,
innerRef: this.storeItemsContainerReference,
},
children: renderedItems,
});
Expand Down
4 changes: 2 additions & 2 deletions test/autowhatever/render-items-container/AutowhateverApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import items from './items';
export const renderItem = (item) => item.text;

export const renderItemsContainer = sinon.spy(
({ containerProps, children }) => (
<div {...containerProps}>
({ containerProps: { innerRef, ...otherContainerProps }, children }) => (
<div {...otherContainerProps} ref={innerRef}>
{children}
<div className="my-items-container-footer">Footer</div>
</div>
Expand Down
65 changes: 33 additions & 32 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export const clearEvents = () => {
eventsArray = [];
};

export const addEvent = event => {
export const addEvent = (event) => {
eventsArray.push(event);
};

export const getEvents = () => {
return eventsArray;
};

export const init = application => {
export const init = (application) => {
app = application;
container = TestUtils.findRenderedDOMComponentWithClass(
app,
Expand All @@ -43,7 +43,7 @@ export const init = application => {
};

// Since react-dom doesn't export SyntheticEvent anymore
export const syntheticEventMatcher = sinon.match(value => {
export const syntheticEventMatcher = sinon.match((value) => {
if (typeof value !== 'object' || value === null) {
return false;
}
Expand All @@ -60,17 +60,18 @@ export const containerPropsMatcher = sinon.match({
id: sinon.match.string,
key: sinon.match.string,
className: sinon.match.string,
ref: sinon.match.func
innerRef: sinon.match.func,
});

const reactAttributesRegex = / data-react[-\w]+="[^"]+"/g;

// See: http://stackoverflow.com/q/28979533/247243
const stripReactAttributes = html => html.replace(reactAttributesRegex, '');
const stripReactAttributes = (html) => html.replace(reactAttributesRegex, '');

export const getInnerHTML = element => stripReactAttributes(element.innerHTML);
export const getInnerHTML = (element) =>
stripReactAttributes(element.innerHTML);

export const getElementWithClass = className =>
export const getElementWithClass = (className) =>
TestUtils.findRenderedDOMComponentWithClass(app, className);

export const expectContainerAttribute = (attributeName, expectedValue) => {
Expand All @@ -81,10 +82,10 @@ export const expectInputAttribute = (attributeName, expectedValue) => {
expect(input.getAttribute(attributeName)).to.equal(expectedValue);
};

export const getSuggestionsContainerAttribute = attributeName =>
export const getSuggestionsContainerAttribute = (attributeName) =>
suggestionsContainer.getAttribute(attributeName);

export const expectInputValue = expectedValue => {
export const expectInputValue = (expectedValue) => {
expect(input.value).to.equal(expectedValue);
};

Expand All @@ -100,7 +101,7 @@ export const getSuggestions = () =>
'react-autosuggest__suggestion'
);

export const getSuggestion = suggestionIndex => {
export const getSuggestion = (suggestionIndex) => {
const suggestions = getSuggestions();

if (suggestionIndex >= suggestions.length) {
Expand Down Expand Up @@ -137,7 +138,7 @@ export const getTitles = () =>
'react-autosuggest__section-title'
);

export const getTitle = titleIndex => {
export const getTitle = (titleIndex) => {
const titles = getTitles();

if (titleIndex >= titles.length) {
Expand All @@ -148,18 +149,18 @@ export const getTitle = titleIndex => {
};

export const expectInputReferenceToBeSet = () => {
expect(app.input).to.equal(input);
expect(app.getInput()).to.equal(input);
};

export const expectSuggestions = expectedSuggestions => {
export const expectSuggestions = (expectedSuggestions) => {
const suggestions = getSuggestions().map(
suggestion => suggestion.textContent
(suggestion) => suggestion.textContent
);

expect(suggestions).to.deep.equal(expectedSuggestions);
};

export const expectHighlightedSuggestion = suggestion => {
export const expectHighlightedSuggestion = (suggestion) => {
const highlightedSuggestions = TestUtils.scryRenderedDOMComponentsWithClass(
app,
'react-autosuggest__suggestion--highlighted'
Expand Down Expand Up @@ -191,56 +192,56 @@ export const expectDontLetBrowserHandleKeyDown = () => {
expect(getEvents()).to.deep.include('onKeyDown-defaultPrevented');
};

export const mouseEnterSuggestion = suggestionIndex => {
export const mouseEnterSuggestion = (suggestionIndex) => {
Simulate.mouseEnter(getSuggestion(suggestionIndex));
};

export const mouseLeaveSuggestion = suggestionIndex => {
export const mouseLeaveSuggestion = (suggestionIndex) => {
Simulate.mouseLeave(getSuggestion(suggestionIndex));
};

export const mouseDownSuggestion = suggestionIndex => {
export const mouseDownSuggestion = (suggestionIndex) => {
Simulate.mouseDown(getSuggestion(suggestionIndex));
};

const mouseDownDocument = target => {
const mouseDownDocument = (target) => {
document.dispatchEvent(
new window.CustomEvent('mousedown', {
detail: {
// must be 'detail' accoring to docs: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events#Adding_custom_data_–_CustomEvent()
target
}
target,
},
})
);
};

export const mouseUpDocument = target => {
export const mouseUpDocument = (target) => {
document.dispatchEvent(
new window.CustomEvent(
'mouseup',
target
? {
detail: {
// must be 'detail' accoring to docs: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events#Adding_custom_data_–_CustomEvent()
target
}
target,
},
}
: null
)
);
};

const touchStartSuggestion = suggestionIndex => {
const touchStartSuggestion = (suggestionIndex) => {
Simulate.touchStart(getSuggestion(suggestionIndex));
};

const touchMoveSuggestion = suggestionIndex => {
const touchMoveSuggestion = (suggestionIndex) => {
Simulate.touchMove(getSuggestion(suggestionIndex));
};

// It doesn't feel right to emulate all the DOM events by copying the implementation.
// Please show me a better way to emulate this.
export const clickSuggestion = suggestionIndex => {
export const clickSuggestion = (suggestionIndex) => {
const suggestion = getSuggestion(suggestionIndex);

mouseEnterSuggestion(suggestionIndex);
Expand All @@ -254,7 +255,7 @@ export const clickSuggestion = suggestionIndex => {
};

// Simulates only mouse events since on touch devices dragging considered as a scroll and is a different case.
export const dragSuggestionOut = suggestionIndex => {
export const dragSuggestionOut = (suggestionIndex) => {
const suggestion = getSuggestion(suggestionIndex);

mouseEnterSuggestion(suggestionIndex);
Expand All @@ -264,7 +265,7 @@ export const dragSuggestionOut = suggestionIndex => {
mouseUpDocument();
};

export const dragSuggestionOutAndIn = suggestionIndex => {
export const dragSuggestionOutAndIn = (suggestionIndex) => {
const suggestion = getSuggestion(suggestionIndex);

mouseEnterSuggestion(suggestionIndex);
Expand All @@ -281,7 +282,7 @@ export const dragSuggestionOutAndIn = suggestionIndex => {

// Simulates mouse events as well as touch events since some browsers (chrome) mirror them and we should handle this.
// Order of events is implemented according to docs: https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent
export const dragSuggestionOutTouch = suggestionIndex => {
export const dragSuggestionOutTouch = (suggestionIndex) => {
touchStartSuggestion(suggestionIndex);
touchMoveSuggestion(suggestionIndex);
mouseDownSuggestion(suggestionIndex);
Expand Down Expand Up @@ -329,12 +330,12 @@ export const clickUp = (count = 1) => {
}
};

export const setInputValue = value => {
export const setInputValue = (value) => {
input.value = value;
Simulate.change(input);
};

export const focusAndSetInputValue = value => {
export const focusAndSetInputValue = (value) => {
focusInput();
setInputValue(value);
};
Expand Down
Loading