downshift version: 3.2.10node version: 10.9.0npm (or yarn) version: 6.2.0Relevant code or config
const Select = () => {
const menuRef = useRef(null);
return (
<Downshift>
{({ getMenuProps, getInputProps }) => (
<div>
<input {...getInputProps()} />
<div {...getMenuProps({ ref: menuRef })}>...options</div>
</div>
)}
</Downshift>
);
};
What you did:
I passed an object-style ref to getMenuProps
What happened:
I got an error:
downshift.esm.js:236 Uncaught TypeError: fn.apply is not a function
at downshift.esm.js:236
at Array.forEach (<anonymous>)
at downshift.esm.js:234
...
Reproduction repository:
Problem description: Downshift tries to compose external refs, but it assumes all refs are functions.
Suggested solution: Augment ref composition to account for object-style refs. You can wrap Downshift's function ref in another function which calls it, then also sets externalRef.current = element (if external ref is an object) or calls external ref (if it's a function).
I'll create a PR soonish, if time allows. Otherwise this issue should help remind me that I need to.
Can you specify what are you trying to achieve?
If you just want the ref, then pass a refKey to getMenuProps, and get it from the result using destructuring. Like this:
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
{ refKey: 'innerRef' },
{ suppressRefError: true },
)
I'm trying to compose a ref through multiple different sources, including a hook. I'm developing a hook to use Popper.js.
Here's a full example I mocked up of the hook interacting with Downshift. It's not quite functional yet, but getting there:
const SelectDemo = ({ options = ['Apple', 'Orange', 'Bannana', 'Kiwi'] }) => {
const [value, setValue] = React.useState(null);
const anchorRef = React.useRef(null);
const popperProps = usePopper<HTMLDivElement, HTMLInputElement>({
anchorRef,
placement: 'bottom',
fullWidth: true,
overflowPadding: 15,
flip: true,
});
return (
<Downshift onChange={setValue}>
{({
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
isOpen,
inputValue,
highlightedIndex,
selectedItem,
}) => (
<div>
<label {...getLabelProps()}>Enter a fruit</label>
<input {...getInputProps({ ref: anchorRef })} />
{isOpen && (
<ul {...getMenuProps(popperProps)}>
{options
.filter(item => !inputValue || item.includes(inputValue))
.map((item, index) => (
<li
{...getItemProps({
key: item,
index,
item,
style: {
backgroundColor:
highlightedIndex === index ? 'lightgray' : 'white',
fontWeight: selectedItem === item ? 'bold' : 'normal',
},
})}
>
{item}
</li>
))}
</ul>
)}
<div>Value: {value}</div>
</div>
)}
</Downshift>
);
};
(I sorta forgot, I was using getInputProps, but the same principle applies here).
The basic idea is just to be able to establish a ref for my component and provide it to any libraries which need to use it.
I could theoretically use the ref created by Downshift, but that would require me to restructure and establish a new component inside the render prop in order to use a hook. I'm not very excited about changing component boundaries for the sake of technical quirks.
I had thought just passing a ref in would be fine, since Downshift seems to support providing a ref, but of course then I discovered this was only for function refs. If you already support the pattern, I don't think it would be so difficult to support all ref types.
const composeRefs = (ourRef, theirRef) => {
if (typeof theirRef === 'function') {
return (el) => {
ourRef(el);
theirRef(el);
};
} else {
return (el) => {
ourRef(el);
theirRef.current = el;
}
}
};
I'd be happy to drop this function into the places refs are used via PR.
@a-type did you ever figure this out? I'm facing a similar issue with access the DOM node via ref.
Update:
For you internet travelers passing by you can do the following to get the DOM node:
const Select = () => {
const menuRef = useRef(null);
return (
<Downshift>
{({ getMenuProps, getInputProps }) => (
<div>
<input {...getInputProps()} />
<div
{...getMenuProps({
ref: e => {
menuRef.current = e;
}
})}>
...options
</div>
</div>
)}
</Downshift>
);
};
Supply a callback to getMenuProps and you'll have access to the menu. This is poorly documented.
@atomicpages thanks for your comment.
But there is an issue with TypeScript in this approach to set ref in a callback: React.RefObject
Do you have any idea on how to deal with it without casting ref to type 'any'?
@victormovchan TS allows you to mutate current if you type it with | null:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L932
@a-type
you're right, thanks
Most helpful comment
@a-type did you ever figure this out? I'm facing a similar issue with access the DOM node via
ref.Update:
For you internet travelers passing by you can do the following to get the DOM node:
Supply a callback to
getMenuPropsand you'll have access to the menu. This is poorly documented.