javascriptreactjsreact-router-v4

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

Solution

  • 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