This package provides a simple of combining several React refs into a single ref that can passed to a single component or DOM node. See React issue #13029 for more details.
This is most useful when you want to keep a ref to a node inside your component and also forward it outside using React.forwardRef
or pass to a library such as react-beautiful-dnd
.
Both React hooks and class components are supported.
useRefComposer
is a hook for composing refs. Usage example:
import { useRefComposer } from 'react-ref-composer'
export const MyComponent = React.forwardRef((props, outerRef) => {
const innerRef = useRef()
const composeRefs = useRefComposer()
return <div ref={composeRefs(innerRef, outerRef)}>test</div>
})
Here composeRef
is a function that you can call with any number of refs. Both object refs and callback refs are supported. The function returns a single "combined" callback ref.
Make sure to always call composeRef
with the same number of arguments. In cases when conditional passing of a ref is required you can pass any falsy (undefined
, null
, 0
, ""
or false
) value instead to temporary "turn off" the ref, e.g.:
... ref={composeRefs(ref1, a && b && ref2)}
Class components work very similarly, just use createRefComposer
instead:
import { createRefComposer } from 'react-ref-composer'
export class MyComponent {
constructor(props) {
super(props)
this.composeRefs = createRefComposer()
}
render() {
return <div ref={this.composeRefs(ref1, ref2, ......)}>test</div>
}
}
Same rules for composeRef
as above apply.
Why create another library? The main problem with existing libraries, including compose-react-refs
, is that none of them handle changing only one of the passed refs correctly. Case in point:
function MyComponent(){
const composeRefs = useRefComposer()
const ref1 = useCallback(div => console.log('ref1', div), [])
const ref2 = useCallback(div => console.log('ref2', div), [])
const ref3 = useCallback(div => console.log('ref3', div), [])
const [flag, setFlag] = useState(true)
function onSwitch() {
console.log('switching')
setFlag(f => !f)
}
return <div ref={composeRefs(ref1, flag ? ref2 : ref3)}>
<button onClick={onSwitch}>Switch refs</button>
</div>
}
This is what the expected output looks like when the user clicks the button:
ref1 <div>
ref2 <div>
switching
ref2 null
ref3 <div>
So the old ref resets to null
and the new ref is set to the DOM node as expected.
However with compose-react-refs
and other similar libraries this happens:
ref1 <div>
ref2 <div>
switching
ref1 null
ref2 null
ref1 <div>
ref3 <div>
Essentially ref1
goes through reset/set cycle. This will trick the consumer of that ref into thinking that the component got unmounted and remounted again, which can be harmful in some cases (e.g. during drag-and-drop operation) and cause undesired behaviour.