downshift: useMouseAndTouchTracker will not work with React Native
utils.useMouseAndTouchTracker has some code like this:
environment.addEventListener('mousedown', onMouseDown)
environment.addEventListener('mouseup', onMouseUp)
environment.addEventListener('touchstart', onTouchStart)
environment.addEventListener('touchmove', onTouchMove)
environment.addEventListener('touchend', onTouchEnd)
When the component is run in production mode with react-native this will fail because environment is defined like so:
const defaultProps = {
itemToString,
stateReducer,
getA11ySelectionMessage,
scrollIntoView,
circularNavigation: false,
environment:
/* istanbul ignore next (ssr) */
typeof window === 'undefined' ? {} : window,
}
As you can see, if window does not exist, the environment.addEventListener is going to crash every time. I looked into the actual use of this hook (utils.useMouseAndTouchTracker) and it seems as though it is only used in two places, but both for the same purpose (it creates a ref (mouseAndTouchTrackersRef) that is used in the two code snippets below): useCombobox:
const inputHandleBlur = () => {
/* istanbul ignore else */
if (
latestState.isOpen &&
!mouseAndTouchTrackersRef.current.isMouseDown
) {
dispatch({
type: stateChangeTypes.InputBlur,
selectItem: true,
})
}
}
and useSelect:
const menuHandleBlur = () => {
// if the blur was a result of selection, we don't trigger this action.
if (shouldBlurRef.current === false) {
shouldBlurRef.current = true
return
}
const shouldBlur = !mouseAndTouchTrackersRef.current.isMouseDown
/* istanbul ignore else */
if (shouldBlur) {
dispatch({type: stateChangeTypes.MenuBlur})
}
}
const menuHandleMouseLeave = () => {
dispatch({
type: stateChangeTypes.MenuMouseLeave,
})
}
It looks like it is used to determine if the component should blur based on whether the user clicked off of the component vs clicking on the dropdown portion of the component. Would it be better to check if the blur target is the dropdown portion? This would avoid the problem with react native I think? I don’t think there is an equivalent to window.addEventListener(‘mousedown’) in react native.
downshiftversion: latest (looking at current code in master)nodeversion: 16.6npm(oryarn) version: 7.19.1
Relevant code or config See above description.
What you did: Render the component in react native production mode (vs dev mode).
What happened:

Reproduction repository: This can be reproduced easily by just running the useCombobox component in any react native production mode code.
Problem description: See above.
Suggested solution: use the target of the onBlur handler to determine if the component should be blurred instead of looking for a click outside the component.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 3
- Comments: 16 (1 by maintainers)
This is amazing, @DaveWelling , I love it! I am planning to write an article about using downshift & react native and this post will definitely be covered in there, the explanation is top notch!
Will go ahead and implement the changes in the meantime. Thanks a lot!
@silviuaavram @JeroenSchrader Sorry I am slow to reply. You might need to distinguish isReactNative and a new boolean for isReactNativeWeb. I realize that further complicates things, but you really do have a different set of APIs available for all three. It’s probably obvious, but the entire DOM is available with React Native Web, and is absent with React Native using native code. IMO React Native Web is sort of a misnomer caused by the dev being between a rock and a hard place. I think he was trying to provide a common API for both web and native to enable one of the true original objective of React Native (shared code). Sorry if this sounds a little whiney, but the Meta team doesn’t really seem to care at all about sharing code between web and native. I don’t think they have any code that is shared with a web app that they need to support. In any case, I think that’s what the original guy who started React Native Web was trying to rectify.
Created https://github.com/downshift-js/downshift/pull/1489, feel free to take a quick look, I think it should be good to go. @DaveWelling @JeroenSchrader
Thanks again everyone, I’m happy to be able to support React native web in downshift with your help!
@silviuaavram I think I have it figured out for you.
The is.macro.js is used to exclude/include certain code. It outputs variables you can use to identify code specific to an environment and toggle the appearance of that code at build time. If you wrap code like this:
if (!isReactNative) { /*non react-native code here*/}it will be excluded from the react native build. For example: This code:Results in this outputted code in the react native build (
downshift.native.cjs.js):You can verify this for yourself by just opening /dist/downshift.native.cjs.js and performing a find on scrollHighlightedItemIntoView and comparing that to what you find in /dist/downshift.cjs.js.
So to create code that is included/excluded depending on if it is react native web is relatively simple. You need to change
is.macro.jslike this:and
package.jsonlike this:Now builds will output a new file
downshift.nativeweb.cjs.jswhich can be used by react native web users. For instance, this code:Yields this code inside of downshift.nativeweb.cjs.js:
Ah I didn’t know, I expected react-native web to be the same as android/ios, probably because I have never worked with react-native web before.
In that case it’s indeed a good idea to create a
isReactNativeWebmacro and use web api’s whenever that’s the case.I have no idea how
isReactNativeworks, or ifisReactNativeWebcan be created, but it’s something we may need to look into.Also, FYI, https://downshift-js.com now contains RN examples as well, for both the hooks and the component.
I think the fix from @DaveWelling makes sense. I looked into the Downshift component code and this part is ignored when in react native. Will apply the same fix for hooks, so that they work in the same way as Downshift for this part.
Thank you for you help!