Not able to .focus() on Link component using ref in react-router 4

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>
)
}

  • 1

    Well, I can reproduce the “not a function” error using RRv4, but I couldn’t reproduce anything using any innerRef prop. Tried also using RRv5 since v4 and v5 are largely much the same, and there’s no issue with using the ref to focus, but no focus occurs. Can you edit to include a complete minimal reproducible example of your code and explain what you are trying to accomplish using a bit more detail?

    – 

  • Thank you for your response. I uploaded an example of what I’m trying to accomplish. Essentially, just switch focus from the button to the Link onClick, but ref is entirely unresponsive. The best I can find is to wrap it in a container and put the ref there, but I feel there should be a more effective method.

    – 

  • 1

    Yes, that is effectively what I tried here as well. The ref should be created using React.useRef though, instead of createRef, so it’s stable from render to render. This doesn’t change the ref behavior though.

    – 

  • Updated the example. I see you’re getting the same error :/

    – 

  • 1

    Oh, you’re right! It is “focused”, but there’s now indication in the browser that it is, e.g. no focus ring, as you describe. After clicking the button I’m able to keyboard interact with it and the URL updates. “Do you think RR5 would even make a difference” – no, not much of a difference. It’s largely the same as v4 but with a few breaking changes that IIRC were related to the React 15 -> 16 transition. I can write my suggestion up as an answer if you like.

    – 




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>

Edit not-able-to-focus-on-link-component-using-ref-in-react-router-4

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;
}

enter image description here

Leave a Comment