Wrapping a react-router Link in an html button
Asked Answered
S

19

180

Using suggested method: This is the result: A link in the button, Code in between comment lines

I was wondering if there is a way to wrap a Link element from 'react-router' in an HTML button tag using react.

I currently have Link components to navigate pages in my app, but I would like to map that functionality to my HTML buttons.

enter image description here enter image description here

Swam answered 26/2, 2017 at 0:17 Comment(0)
E
278

While this will render in a web browser, beware that:
⚠️Nesting an html button in an html a (or vice-versa) is not valid html ⚠️. If you want to keep your html semantic to screen readers, use another approach.

Do wrapping in the reverse way and you get the original button with the Link attached. No CSS changes required.

 <Link to="/dashboard">
     <button type="button">
          Click Me!
     </button>
 </Link>

Here button is HTML button. It is also applicable to the components imported from third party libraries like Semantic-UI-React.

 import { Button } from 'semantic-ui-react'
 ... 
 <Link to="/dashboard">
     <Button style={myStyle}>
        <p>Click Me!</p>
     </Button>
 </Link>
Equitant answered 7/9, 2017 at 5:49 Comment(8)
Thanks, simple and working, while css solution of @ChaseJames is not working. Maybe there something is missing. But I am using bootstrap; import { Button } from 'react-bootstrap';.Kelila
When tabbing through the document you'll tab to the anchor, and then to the button separately. To avoid this, add tabindex="-1" to the Link element. The link will still be followed (at least in Firefox...) when the button is activated by pressing enter.Pervasive
As you know, Link creates anchor tag <a href=""> and anchor tag cannot contain button tag.Weltschmerz
While this seems to work, it's semantically wrong (and invalid in HTML 5). See Can I nest a <button> element inside an <a> using HTML5?Ufo
If you make your button disabled for example, clicking the button will still work. There's really no reason to use this hacky solution when compared to the history.push optionAttenuator
semantic-ui-react now supports component augmentation, you need to use as={ Link } prop on a regular semantic-ui-react Button component. Use the original props that you would of used in the Link component.Vella
Burak, history prop is not available in version 6 of react-router-dom, for information you can see: https://mcmap.net/q/136222/-wrapping-a-react-router-link-in-an-html-buttonArchi
Use <Button as={Link} to='/dashboard'> <p>Click me</p> </Button> for something cleaner. No extra HTML or react syntax to deal with and no chance for behavior weirdness with tabbing.Remarkable
P
152

LinkButton component - a solution for React Router v4

First, a note about many other answers to this question.

⚠️ Nesting <button> and <a> is not valid html. ⚠️

Any answer here which suggests nesting a html button in a React Router Link component (or vice-versa) will render in a web browser, but it is not semantic, accessible, or valid html:

<a stuff-here><button>label text</button></a>
<button><a stuff-here>label text</a></button>

Click to validate this markup with validator.w3.org

This can lead to layout/styling issues as buttons are not typically placed inside links.


Using an html <button> tag with React Router <Link> component.

If you only want an html button tag…

<button>label text</button>

…then, here's the right way to get a button that works like React Router’s Link component…

Use React Router’s withRouter HOC to pass these props to your component:

  • history
  • location
  • match
  • staticContext

LinkButton component

Here’s a LinkButton component for you to copy/pasta:

// file: /components/LinkButton.jsx
import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router'

const LinkButton = (props) => {
  const {
    history,
    location,
    match,
    staticContext,
    to,
    onClick,
    // ⬆ filtering out props that `button` doesn’t know what to do with.
    ...rest
  } = props
  return (
    <button
      {...rest} // `children` is just another prop!
      onClick={(event) => {
        onClick && onClick(event)
        history.push(to)
      }}
    />
  )
}

LinkButton.propTypes = {
  to: PropTypes.string.isRequired,
  children: PropTypes.node.isRequired
}

export default withRouter(LinkButton)

Then import the component:

import LinkButton from '/components/LinkButton'

Use the component:

<LinkButton to='/path/to/page'>Push My Buttons!</LinkButton>

If you need an onClick method:

<LinkButton
  to='/path/to/page'
  onClick={(event) => {
    console.log('custom event here!', event)
  }}
>Push My Buttons!</LinkButton>

Update: If you're looking for another fun option made available after the above was written, check out this useRouter hook.

Podium answered 22/3, 2018 at 23:10 Comment(9)
This should be the accepted answer. All other answers are, IMHO, hacks and can provide unexpected results. Thank you!Gruver
Would the usage of the component go inside the <div> .. </div element of App.js ?Scrivings
@Scrivings - Yes, or in any other component you want to use it in.Podium
Thanks for this! In my particular use case I imported a material-ui button with import IconButton from '@material-ui/core/IconButton'; and used that instead of <button /> and it worked great.Fewness
it's really smarty to run onClick before making history.push. This way we could check !event.isDefaultPrevented && history.push(...) to make this work like <Link> doesSaskatoon
But you cannot use <a> features here like Ctrl+Click or seeing destination url in footer before clicking :( I am voting for @ChaseJames slution as the cleanest.Rustyrut
@Rustyrut - Hello there! If the goal is to have an <a> html tag visually styled to look like a "button", then yes @ChaseJames solution achieves this. The question above asks how to use a <button> html element, not an <a>.Podium
withRouter is not available in version 6 react-router-dom now, those who are using latest version, they can work with hooks in functional components for details: https://mcmap.net/q/136222/-wrapping-a-react-router-link-in-an-html-buttonArchi
If you want semantic HTML you can do: <button role="link"> for screen readers I believe.Yt
H
64

Why not just decorate link tag with the same css as a button.

<Link 
 className="btn btn-pink"
 role="button"
 to="/"
 onClick={this.handleClick()}
> 
 Button1
</Link>
Hardhack answered 26/2, 2017 at 0:22 Comment(4)
I will keep this as a last resort mostly because I have the CSS done for all the buttons that I need but thanks for the suggestionSwam
Brilliant solution. For blueprint I do in this way: <Link className={[Classes.BUTTON, Classes.MINIMAL, Classes.ICON + "-" + IconNames.PLUS].join(" ")} to={"/link"}>Link</Link>Gerhardine
You can't use enabled/disabled states on a fake button.Omnirange
This is perhaps the best approach, because navigation typically should use link semantics, i.e., <a/> tags. It should be noted though that simply applying an existing CSS class designed for <button/> to an <a/> can lead to broken style. The reason is that these elements are fundamentally different in terms of CSS behavior, see this question. In my case, I needed to refactor styles first to apply this solution.Cirro
V
44

If you are using react-router-dom and material-ui you can use ...

import { Link } from 'react-router-dom'
import Button from '@material-ui/core/Button';

<Button component={Link} to="/open-collective">
  Link
</Button>

You can read more here.

Vernal answered 12/2, 2019 at 12:24 Comment(3)
This solution will not work with TS since to prop on Link is mandatory.Acervate
My mistake was to import Link from @material-ui/core instead of react-router-domEntomophagous
This worked for me in TS - no type errors.Microhenry
M
30

You can use useHistory hook since react-router v5.1.0.

The useHistory hook gives you access to the history instance that you may use to navigate.

import React from 'react'
import { useHistory } from 'react-router-dom'

export default function SomeComponent() {
  const { push } = useHistory()
  ...
  <button
    type="button"
    onClick={() => push('/some-link')}
  >
    Some link
  </button>
  ...
}

NOTE: be aware that this approach answers the question but is not accessible like @ziz194 says in their comment

this is not accessible though, as the button will not be a tag and thus it doesn't have link behaviours, like opening the link in a new page. It is also not optimal for screen readers.

CONCLUSION: The better way to handle this is to use an <a> tag and style it.

Middle answered 13/11, 2019 at 22:1 Comment(8)
Can you push "some-link" to the function?Gayla
In 2020 this does not work for me. useHistory is null in my version of ReactGentle
The import should read 'react-router-dom' not 'react-router'Convocation
Hey @Rafael, thank you. I changed it to react-router-domMiddle
This is probably the most straightforward solution as of 2021.Histolysis
this is not accessible though, as the button will not be a <a> tag and thus it doesn't have link behaviours, like opening the link in a new page. It is also not optimal for screen readers.Colum
@ziz194, I just added your comment in my answer. Thanks for adding value.Middle
In react-router-dom version 6, you are not able to use useHistory instead you can useNavigate.Archi
R
20

Update for React Router version 6:

The various answers here are like a timeline of react-router's evolution 🙂

Using the latest hooks from react-router v6, this can now be done easily with the useNavigate hook.

import { useNavigate } from 'react-router-dom'      

function MyLinkButton() {
  const navigate = useNavigate()
  return (
      <button onClick={() => navigate("/home")}>
        Go Home
      </button>
  );
}
Redaredact answered 17/10, 2020 at 22:18 Comment(3)
version 6 isn't available yet and this answer is confusing because of that. latest as of this comment is 5.2.0.Calceolaria
version 6 is currently available in beta and being used in some projects. Please see the Versions tab here: npmjs.com/package/react-routerRedaredact
Using buttons for navigation is semantically incorrect, <a> is for navigation, not <button>Trunnion
O
10

With styled components this can be easily achieved

First Design a styled button

import styled from "styled-components";
import {Link} from "react-router-dom";

const Button = styled.button`
  background: white;
  color:red;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid red;
  border-radius: 3px;
`
render(
    <Button as={Link} to="/home"> Text Goes Here </Button>
);

check styled component's home for more

Obstruction answered 11/1, 2019 at 13:32 Comment(0)
V
7

I use Router and < Button/>. No < Link/>

<Button onClick={()=> {this.props.history.replace('/mypage')}}>
   HERE
</Button>
Varityper answered 7/4, 2018 at 17:28 Comment(2)
I think this should be the default solution, but perhaps some details are lost to me? ANY component/node/element that support onClick={ () => navigateTo(somePath) } could use this approach. Whether using redux and import {push} from 'connected-react-router' or just history.push (or replace) like in your answer.Euplastic
this solution is not accessible, you cannot open the link in a new page using this solution.Colum
D
6

⚠️ No, Nesting an html button in an html a (or vice-versa) is not valid html

Dendriform answered 26/2, 2017 at 0:23 Comment(15)
So I tried this, and what I got was a button with a small clickable link in the centre that served its function. However clicking anywhere else on the button but the direct link did nothing.Swam
You can use CSS to style the button to ensure that it is sized correctly. For example, setting it's width and height.Dendriform
Oh I am aware. I am using CSS to give each button a background image, and set its behaviour to become slightly larger when hovered over. So its width x height are set to match the image size. I just need the WHOLE button to link to the path and to not just create a linkable line of text in the middle of the button. Also removing the text did nothing.Swam
Can you screenshot the issue so I can see?Dendriform
Sure. Uploaded to top of pageSwam
I see what you mean now. We need to pass the <button>s onClick method to <Link />s. Thinking of how best to do that.Dendriform
What if you styled the <button>s child <a> tag to have the same height and width as it's parent? display: block; would work on the <a> tag.Dendriform
@JoseRivera Updated.Dendriform
Tried to do that, but not sure what you meant.Swam
How can I style a button's a tag?Swam
You can use inline styles the same way for the button tag. Or you can use one of the many "css in JS" libraries. I prefer styled-components. github.com/styled-components/styled-componentsDendriform
Thanks for your help. I'm going to play around with this some more later.Swam
Should be recorded but wont display my upvote until my account reaches 15 rep. Will be sure to update it once it does!Swam
It worked with your updated answer. Thank you so much!Swam
This is not correct... You should wrap the button, not the link 😱Toughie
T
6

For anyone looking for a solution using React 16.8+ (hooks) and React Router 5:

You can change the route using a button with the following code:

<button onClick={() => props.history.push("path")}>

React Router provides some props to your components, including the push() function on history which works pretty much like the < Link to='path' > element.

You don't need to wrap your components with the Higher Order Component "withRouter" to get access to those props.

Tricolor answered 19/7, 2019 at 14:9 Comment(2)
This works fine for me; though I'm not sure what about this solution is specific to React 16.8+ or hooks. I think you're in good shape with this so long as you employ BrowserRouter (based on the HTML history API) instead of HashRouter.Inappreciative
Navigation on click for non-Link elements is meh. You can't interact as you would with Link, so no "open in a new tab" features.Drumbeat
H
2
<Button component={Link} to="/dashboard" type="button">
    Click Me!
</Button>
Heady answered 16/10, 2021 at 15:23 Comment(1)
While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. You can find more information on how to write good answers in the help center: stackoverflow.com/help/how-to-answer . Good luck 🙂Porphyroid
P
1

Many of the solutions have focused on complicating things.

Using withRouter is a really long solution for something as simple as a button that links to somewhere else in the App.

If you are going for S.P.A. (single page application), the easiest answer I have found is to use with the button's equivalent className.

This ensures you are maintaining shared state / context without reloading your entire app as is done with

import { NavLink } from 'react-router-dom'; // 14.6K (gzipped: 5.2 K)

// Where link.{something} is the imported data
<NavLink className={`bx--btn bx--btn--primary ${link.className}`} to={link.href} activeClassName={'active'}>
    {link.label}
</NavLink>

// Simplified version:
<NavLink className={'bx--btn bx--btn--primary'} to={'/myLocalPath'}>
    Button without using withRouter
</NavLink>
Perjury answered 3/4, 2019 at 15:29 Comment(2)
This didn't work for me. The hove style of the button is lost v_v.Burnside
Perhaps your hover styles are too restrictive? Are they assigned to button.myClassName? Remove button from the style selector. Or extend it in scss.Perjury
M
1

I recommend that you utilize the component prop on the Link component. Using this, you can have effectively any component behave as an a component from the perspective of React Rotuer. For instance, you can create your own "Button" component and then utilize this.


const MyButton = () => {
    return <button>Do something with props, etc.</button>
}

<Link to="/somewhere" component={MyButton}>Potentially pass in text here</Link>
Metaplasm answered 29/3, 2021 at 22:39 Comment(1)
I agree, this is how it's meant to be used. Personally I would write it this way though: <Link to="/somewhere" component={c => <button {...c} />}>Pass in text here</Link>Merola
D
1

Using react-router-dom and a function

In react, you can use react-router-dom by applying the useHistory call...

- Firstly

import { useHistory } from 'react-router-dom';

- Secondly Inside your function...write a function to handle the button click

const handleButtonClick = () => {
    history.push('/YourPageLink')
  }

- Lastly

<button onClick={handleButtonClick} className="CSS">
   Button Text
</button>
Dygal answered 10/8, 2021 at 20:0 Comment(2)
in version 6, useHistory is not applicable you can use useNavigate insteadArchi
Yes, useNavigate has been newly introduced.Dygal
A
1

Just useLinkClickHandler (for react-router version 6)

const ButtonLink = ({to, children, ...rest}) => {
  const handleClick = useLinkClickHandler(to);

  return (
    <button onClick={handleClick} {...rest}>
      {children}
    </button>
  );
};
Annalist answered 18/8, 2023 at 10:41 Comment(0)
A
0

As I searched through answers everyone was talking about the old versions of react-router-dom, so this might be useful answer for those who want to use the hooks in functional components. As in the new version few hooks are changed and even the withRouter is not applicable you need to make a custom HOC.

we can achieve it through functional components you can use your button and then describe onClick event to trigger to the next page through useNavigate hook.

React-Router-Dom version 6


this is only possible in functional components as i am using hooks, if you want to use on class components then you can make hoc.

import {Link, useNavigate} from 'react-router-dom';
function Home() {
you can pass your state like so:  navigate('/show',{state});
const toShowInfoPage=()=>{
navigate('/show');

  }


return(
<>


 /*you can use button as well or anything 
of your choice, but remember 
you have to pass the onClick event.
    */
 <a 
onClick={()=>{toShowInfoPage()}} 
style={{textDecoration:'none',
paddingRight:'20px',
color:'#187bcd',
cursor:'pointer'}}>


</>

)



}

To access useNavigate() with a class component you must either convert to a function component, or roll your own custom withRouter Higher Order Component to inject the "route props" like the withRouter HOC from react-router-dom v5.x did.


This is just a general idea. if you want to useNavigate Here's an example custom withRouter HOC:

const withRouter = WrappedComponent => props => {
  const params = useParams();
  // etc... other react-router-dom v6 hooks

  return (
    <WrappedComponent
      {...props}
      params={params}
      // etc...
    />
  );
};

And decorate the component with the new HOC.

export default withRouter(Post);

This will inject a props for the class component.

Archi answered 19/11, 2021 at 10:46 Comment(0)
S
0

You can use the component prop: https://v5.reactrouter.com/web/api/Link/component-reactcomponent

For example:

<Link to="/login" component={({ navigate}) => <button onClick={navigate}>Login</button>} />
Surety answered 22/6, 2022 at 8:33 Comment(0)
P
0

For react-router-dom v5 you can simply style the link itself to look like button:

import {Link} from "react-router-dom";

<Link className="btn" to="/myurl">Submit</Link>

where className="btn" refers to the css class name, which makes it look like button.

Or you can define the following object:

export function ButtonLink(props) {
    const { as: Component, children, to, className } = props;
    return <Component className={className} to={to}>{children}</Component>
}

and then use it

import {ButtonLink} from "../../components/ButtonLink";
import {Link} from "react-router-dom";

<ButtonLink as={Link} to="/myUrl" className="btn">Submit</ButtonLink>

but you will still have to refer to css to make it look like button (className="btn"), so first approach is the easiest.

Preposterous answered 9/3, 2023 at 14:17 Comment(0)
H
0

very simple - javascript part:

    <script>

    function gofor(link) {
        location.href = link;
    }

    </script>

and here the html part:

    <button onclick="gofor('link.html');">Click Me!</button>

Thats all.

Halie answered 4/7, 2023 at 18:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.