I am using the Link component from react-router (v4). I am able to attach a ref to this component and console log the reference.
However, if I call linkRef.current.focus() I get the error:
linkRef.current.focus() is not a function
I am able to attach innerRef to the Link component and .focus will work. But when I do this, I get an error in the console that states 'Warning: Failed prop type: Invalid prop 'innerRef' supplied to 'Link', expected one of type [string, function]`
Is there a way I can use a ref to focus on a Link component in react-router 4 or at least prevent the innerRef error?
EXAMPLE:
The example below shows what I am trying to accomplish. Essentially, when the button is clicked, I'm attempting to shift focus to the link element that has the ref on it. While this will work and change focus when I user innerRef instead of ref as the prop on the LinkWrapper, it will throw the console error.
However, if I use ref nothing happens and the focus remains on the button.
const TestComponent = () => {
const linkRef = React.useRef()
const testFunction = () => {
linkRef?.current?.focus()
}
return (
<div>
<button onClick={() => testFunction()}>Focus button here</button>
<Link ref={linkRef} to={'/testUrl.com'}>Test Here</Link>
</div>
)
}
The react-router@4
Link
component doesn't forward any React refs like was introduced in react@16
, but it surfaces an innerRef
prop that can pass a React ref through.
See react-router@4
Link
class Link extends React.Component { handleClick(event, history) { if (this.props.onClick) this.props.onClick(event); if ( !event.defaultPrevented && // onClick prevented default event.button === 0 && // ignore everything but left clicks (!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc. !isModifiedEvent(event) // ignore clicks with modifier keys ) { event.preventDefault(); const method = this.props.replace ? history.replace : history.push; method(this.props.to); } } render() { const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars return ( <RouterContext.Consumer> {context => { invariant(context, "You should not use <Link> outside a <Router>"); const location = typeof to === "string" ? createLocation(to, null, null, context.location) : to; const href = location ? context.history.createHref(location) : ""; return ( <a {...rest} onClick={event => this.handleClick(event, context.history)} href={href} ref={innerRef} /> ); }} </RouterContext.Consumer> ); } } const innerRefType = PropTypes.oneOfType([ PropTypes.string, PropTypes.func, PropTypes.shape({ current: PropTypes.any }) ]); Link.propTypes = { innerRef: innerRefType, onClick: PropTypes.func, replace: PropTypes.bool, target: PropTypes.string, to: toType.isRequired };
It looks like a normal (by today's standards) React ref would be allowed to be passed through, but it doesn't quite have the correct shape it seems. You can use the old legacy function method of setting a React ref though.
innerRef={ref => linkRef.current = ref}
Code:
<div>
<button type="button" onClick={() => linkRef.current.focus()}>
Focus Link
</button>
</div>
<Link innerRef={(ref) => (linkRef.current = ref)} to={"/testUrl.com"}>
Test Here
</Link>
The anchor tag <a>
has browser focus and is interactable, all that appears to be missing is some CSS to apply visible focus.
a:focus {
outline: 2px red dashed;
}