Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add <Link component> prop #6462

Merged
merged 1 commit into from Jun 13, 2019
Merged

Add <Link component> prop #6462

merged 1 commit into from Jun 13, 2019

Conversation

mjackson
Copy link
Member

@mjackson mjackson commented Nov 7, 2018

This PR adds a <Link component> prop that links can use to render a custom component instead of an <a>.

This has been an oft-requested feature over the years that we've traditionally not wanted to add because all links on the web should be <a> tags for accessibility reasons. However, this limitation means that we have different APIs between react-router-dom's <Link> and react-router-native's <Link>, which makes it difficult to reuse code across native and web.

For previous discussion, see #4625 and #5437

/cc @necolas @LazyElephant

@mjackson mjackson added this to the 4.4 milestone Nov 7, 2018
@mjackson mjackson modified the milestones: 4.4, 4.5 Nov 8, 2018
@necolas
Copy link

necolas commented Nov 8, 2018

Oh, yes, they're the same API but different prop names, so having an intermediate component / function to map the other prop names would work! Sorry

@mjackson
Copy link
Member Author

mjackson commented Nov 8, 2018

OK, I just pushed another version. This one works like this:

function MyLink({ href, innerRef, navigate, ...rest }) {
  return <button {...rest} ref={innerRef} onClick={navigate} />;
}

<Link to="/the/url" component={MyLink}>click me</Link>

The props are:

  • navigate - This is the function you call to follow the link
  • href - This is the full href value that you should use in an <a> tag should you decide to render one so it's accessible. It might also be useful for other purposes, not sure yet.
  • innerRef - You should apply this ref to the component you render (In version 5 we can drop this and tell people to use forwardRef instead)

All other props that you pass to your <Link> (including style, children, etc., but not those that are specific to the <Link> API, like to, replace, etc.) also come through to your component (in ...rest), so you can do whatever you want with them.

How does that sound?

@mjackson
Copy link
Member Author

mjackson commented Nov 8, 2018

I should also say that I'd be happy to pass those same props to a children render prop, same as we do with our <Route> API. So:

<Link to="/some/url">
  {({ href, navigate, ...rest }) => (
    <button {...rest} onClick={navigate} />
  )}
</Link>

@necolas
Copy link

necolas commented Nov 8, 2018

What do you think about exposing the logic for determining if a navigation should be handled by the router?

const MyComponent = ({ children, href, navigate }) =>
  <Text
    accessibilityRole="link"
    children={children}
    href={href}
    onPress={(e) => {
      if (Link.shouldEventNavigate(e)) {
        navigate()
      }
    }}
  />

@mjackson
Copy link
Member Author

mjackson commented Nov 8, 2018

Are you thinking about the use case we discussed at React Conf? Where the back button behaved differently in the native and mobile web versions when the modal was showing? I don't understand what logic would be exposed in shouldEventNavigate.

@necolas
Copy link

necolas commented Nov 8, 2018

No just the logic that filters clicks with modified keys, right click, etc. The latest diff locks that away in the default link renderer

@mjackson
Copy link
Member Author

mjackson commented Nov 8, 2018

Sure, we could do that. Alternatively, would it make sense for navigate to return a boolean that let you know whether or not the router handled navigation?

const MyComponent = ({ children, href, navigate }) =>
  <Text
    accessibilityRole="link"
    children={children}
    href={href}
    onPress={(e) => {
      if (!navigate(e)) {
        // router didn't navigate, handle navigation yourself
      }
    }}
  />

@mjackson
Copy link
Member Author

mjackson commented Nov 8, 2018

Is the onPress event in RNW actually a click event object? Or do you create your own event object?

@necolas
Copy link

necolas commented Nov 8, 2018

At the moment it is but won't be forever. Thinking more about people using React DOM and making their own links.

One of the limitations of navigate returning a value is you have to navigate to get it, which excludes the possibility of people wanting to do something custom only if navigation is expected.

@devuxer
Copy link

devuxer commented Feb 23, 2019

@mjackson ,

Just curious about the claim that...

all links on the web should be <a> tags for accessibility reasons

Does this apply to form submit buttons (such as a "Log In" button)?

@timdorr
Copy link
Member

timdorr commented Feb 23, 2019

@devuxer That's a button, not a link. It doesn't behave like a link; it submits a form. Totally different thing.

@devuxer
Copy link

devuxer commented Feb 24, 2019

@timdorr,

If the user clicks on something and it results in the browser navigating to a different page, that is the exact behavior of a link. Whether something else occurs (like persisting data to a database) before navigating doesn't make it a"totally different thing" in my opinion.

Bottom line, the ability to navigate from an on-click handler should be a core feature of a router. Currently, it seems that you have to push to the history object, and the history object has to be passed into the component via HOC or through props (unless you use context, which is discouraged in the docs).

@pshrmn
Copy link
Contributor

pshrmn commented Feb 24, 2019

@devuxer in a multi page application, a login button would submit the form to the server, which (if the login is authenticated) would return a redirect response. Clicking the button results in navigation, but it is not a link.

The same is true for an SPA. The user clicks a button, and if the login is authenticated, the app can redirect to another page.

The purpose of this PR isn't for buttons that submit forms, but for buttons (or other components) that are link-like (which is generally a bad idea, hence the current inability to pass a custom component).

@devuxer
Copy link

devuxer commented Feb 24, 2019

@pshrmn,

Ah, I see your point that a redirect is a different thing (though it's pretty much the same from the user's point of view).

And I agree that this PR isn't really the navigate feature I'm looking for. I actually stumbled upon this issue because I was trying to figure out how to get a navigation menu item to behave as a link, but then I was sidetracked by the claim that all links should be a tags.

@necolas
Copy link

necolas commented Feb 24, 2019

The purpose of this PR isn't for buttons that submit forms, but for buttons (or other components) that are link-like

No, the PR is to support composite components that render down to a to be used if custom presentation or behaviour is desired.

@mjackson mjackson removed this from the 5.1 milestone Mar 26, 2019
@timdorr
Copy link
Member

timdorr commented Jun 12, 2019

Rebased against the latest master.

@timdorr
Copy link
Member

timdorr commented Jun 13, 2019

OK, it works. I'm merging it in.

@timdorr timdorr merged commit 520a0ac into master Jun 13, 2019
@timdorr timdorr deleted the link-component branch June 13, 2019 00:14
@lock lock bot locked as resolved and limited conversation to collaborators Aug 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants