Open FrameMuse opened 3 months ago
Hey @FrameMuse, I checked your reference npm package and whole scenario and it seems like worth adding, but I haven't gotten around to such a scenario as when I need to have a ref for parent and child both component, can you please explain the use case further more so I can better understand the requirement.
I am willing to work on this feature and raise PR if the scenario is really useful and maintainers are happy to add this feature.
@monikkacha
Here's two use-cases and my real-world example, hope this would be helpful.
In React 19 I can use ref
in props
, put in an element and use it in the component itself while giving an outside access to it as well.
function Component(props: { ref: Ref<HTMLDivElement> }) {
useClickAway(props.ref)
return <div ref={props.ref} />
}
function Parent() {
const elementRef = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
elementRef.current.clientHeight // Will actually work now.
}, [])
return <Component ref={elementRef} />
}
But the problem comes when you want to make that ref
to be optional.
function Component(props: { ref?: Ref<HTMLDivElement> }) {
useClickAway(props.ref) // Won't work if `ref` is not passed.
return <div ref={props.ref} />
}
// Click Away will work only for component that is receiving a ref.
<Component ref={elementRef} />
<Component />
That's why we need to use a fallback ref.
function Component(props: { ref?: Ref<HTMLDivElement> }) {
const elementRef = useRef<HTMLDivElement>(null)
const combinedRef = combineRefs([elementRef , props.ref])
useClickAway(elementRef) // Now will work regardless of passed `ref`.
return <div ref={combinedRef} />
}
// Click Away will work for both components.
<Component ref={elementRef} />
<Component />
Concurrent happens when you have ref that is coming from a parent but your element also have a ref, you may say
Just use the ref that the parent is passing (-_-)/
This will actually work, but only in cases when the parent is passing an object.
If it's a callback, it won't work that easily, you will have to use useEffect
to call passing ref
, which is not ideal and may have consequences.
To solve it, you will need to pass a callback to the element and call or assign your refs. That's why there is a library that provides a function for that.
I used TanStack Virtual to virtualize the list and I used ref
to measure element dynamically, but I wanted the components of list to have possibility of click away.
In this case I had ref
as a callback though components needed a ref object. It's somewhat close to a fallback case.
interface ItemsProps {
items: object[]
}
function Items(props: ItemsProps) {
const elementRef = useRef<HTMLDivElement>(null)
const scrolling = useContext(scrollingContext)
const virtual = useVirtualizer({
count: props.items.length,
gap: 8,
getScrollElement: () => scrolling.elementRef.current,
estimateSize: () => 60,
measureElement: element => element.scrollHeight,
overscan: 5
})
const items = virtual.getVirtualItems()
return (
<div className="items" style={{ height: virtual.getTotalSize(), zIndex: 0 }} ref={elementRef}>
{items.map(item => (
<div style={{ height: item.size, transform: `translateY(${item.start}px)`, zIndex: -item.index }} key={item.key}>
<Item {...props.items[item.index]} index={item.index} ref={virtual.measureElement} />
</div>
))}
</div>
)
}
interface ItemProps {
index: number
ref?: Ref<HTMLDivElement>
}
function Item(props: ItemProps) {
const elementRef = useRef<HTMLDivElement>(null)
const combinedRef = combineRefs([elementRef, props.ref])
const [expanded, setExpanded] = useState(false)
useClickAway(elementRef, () => setExpanded(false))
return <div onClick={() => setExpanded(true)} ref={combinedRef} />
}
The code is simplified and generalized to not disclose any source code
@eps1lon is it ok if i work on this issue and raise PR ?
Sure, but no promises when this will be reviewed or if it will be merged at all.
That sounds depressing 😅, but I will still work on the feature and raise PR later on.
Summary
I wanted to use
ref
fromprops
while also creating a localref
as a fallback, but I got into this problem https://github.com/facebook/react/issues/17200.I know this could be resolved with a little code, though I think this behavior should belong natively to React. There is even a package for that https://www.npmjs.com/package/react-merge-refs with ~1 million downloads/week, which confirms necessity of this for the React users.
I would propose something like
or at least this for fallback cases
or hook/helper to resolve
ref
s concurrencyWhat do you think? Am I going too far with it? 😅