Ant-design-mobile: Button use anchor tag issue

Created on 24 Feb 2017  ·  11Comments  ·  Source: ant-design/ant-design-mobile

Environment(required)

  • antd-mobile version: 1.0.0

What did you do? Please provide steps to re-produce your problem.

Using Button as an Link is a common scenario. In previous version( 0.9.*), Button Component use button tag by default with an htmlType prop, which is fine when I used React-router Link (which is an anchor) to wrap Button.

What happen?

After updating to 1.0.0, The Button Component use anchor tag , I get warnings about anchor nested in anchor

What do you expected?

I shall say this is not a bug and I can replace React-router Link with router methods to get rid of
those warnings.
I'm just wondering on what kind of considerations that you changed the button component to use anchor instead of button.

Re-producible online demo

enhancement question

Most helpful comment

@kayyyy that relies on implementation details (classnames) which is not great. Also, you're no longer using the Button component, so you lose out on all the benefits (eg: disabled button, loading status, icon...etc).

The Button component should really allow you to override the underlying component, eg:

import { Link } from 'react-router-dom';
import { Button } from 'antd';

<Button component={Link} to="/my-link">Link</Button>

This is a popular approach among react ui libraries, for example material ui:
https://material-ui.com/demos/buttons/#third-party-routing-library

All 11 comments

  • fisrt, why you did anchor nested in anchor ? Are you put a <Link> in <Button> ? That is unnecessary

  • why use a rather than button, possibly because of this

Before the update, I use button like this

<Link><Button>...</Button><Link>

which results in

<a><button>...</button></a>

because many button-like elements are actually links and I did't have to add extra page jumping event handlers to them.
After the update, those warnings showed up.

consider change Button inner element to div in next release.

cc @silentcloud @warmhug

@yup9

Sorry, i decided not to change this, since i looked at many other open source ui components,
use <a> for a Button is a normal way, which avoid native button's ugly style and still keep good Accessible Rich Internet Applications (ARIA) . ref https://github.com/ant-design/ant-design-mobile/pull/897

I suggest you use onClick callback to do router change to avoid the warning.

going to close this, if you have any further question, feel free to repoen :)

I'm ok with this. Thanks for your time.😄

Why not just giving the <Link> the needed className?

<Link className="ant-btn ant-btn-primary" to="/yoururl">Your Text</Link>

@kayyyy that relies on implementation details (classnames) which is not great. Also, you're no longer using the Button component, so you lose out on all the benefits (eg: disabled button, loading status, icon...etc).

The Button component should really allow you to override the underlying component, eg:

import { Link } from 'react-router-dom';
import { Button } from 'antd';

<Button component={Link} to="/my-link">Link</Button>

This is a popular approach among react ui libraries, for example material ui:
https://material-ui.com/demos/buttons/#third-party-routing-library

Hello guys, I definitely agree with @VictorChen. It looks to me the soundest way to go.

Meanwhile I use this ugly workaround when it comes to react-router:

import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'

import Button from 'antd-mobile/lib/button'
import 'antd-mobile/lib/button/style/css'

const ButtonAntd = ({
    linkTo,
    linkMode,
    match,
    location,
    history,
    staticContext,
    ...props
}) =>
    linkTo
        ? (
            <Button
                {...props}
                onClick={() => {
                    if (linkMode === 'replace') {
                        history.replace(linkTo)
                    } else {
                        history.push(linkTo)
                    }
                }}
            />
        )
        : <Button {...props} />

ButtonAntd.propTypes = {
    linkTo: PropTypes.string,
    linkMode: PropTypes.oneOf(['replace']),
    location: PropTypes.object.isRequired,
    match: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
    staticContext: PropTypes.object,
}

ButtonAntd.defaultProps = {
    linkTo: null,
    linkMode: null,
    staticContext: null,
}

export default withRouter(ButtonAntd)

Hello any updates on this? It is such a simple update, to allow pass tag prop. It would solve lots of issues. All other libraries has it

I've written simple wrapper for Button. Thought this might help you all until we have some more flexibility on Buttons underying tag. This solution also supports opening link in new tab 🙂

import Button, { ButtonProps } from "antd/lib/button";
import { createLocation, Location, LocationDescriptor } from "history";
import React from "react";
import { __RouterContext as RouterContext } from "react-router";
import invariant from "tiny-invariant";

const normalizeToLocation = (to: LocationDescriptor, currentLocation: Location) => {
    return typeof to === "string" ? createLocation(to, null, undefined, currentLocation) : to;
};

function isModifiedEvent(event: any) {
    return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}

type LinkButtonProps = ({ to: LocationDescriptor; toExternal?: undefined } | { to?: undefined; toExternal: string }) & {
    replace?: boolean;
    "data-test-id"?: string;
} & ButtonProps;

const LinkButton: React.FunctionComponent<LinkButtonProps> = ({
    "data-test-id": dataTestId,
    replace,
    to,
    onClick,
    toExternal,
    ...rest
}) => {
    return (
        <RouterContext.Consumer>
            {context => {
                invariant(context, "You should not use <ButtonLink> outside a <Router>");

                const { history } = context;
                const [href, navigate] = ((): [string, (() => void) | undefined] => {
                    if (toExternal) {
                        return [toExternal, undefined];
                    }
                    if (to) {
                        const location = normalizeToLocation(to, context.location);

                        const navigate = () => {
                            const method: (location: LocationDescriptor) => void = replace
                                ? history.replace
                                : history.push;

                            method(to);
                        };

                        return [location ? history.createHref(location) : "", navigate];
                    }

                    throw new Error("Either 'to' or 'toExternal' need to be defined");
                })();

                const { target } = rest;

                return (
                    <Button
                        data-test-id={dataTestId}
                        {...rest}
                        href={href}
                        onClick={
                            navigate &&
                            (event => {
                                try {
                                    onClick && onClick(event);
                                } catch (ex) {
                                    event.preventDefault();
                                    throw ex;
                                }

                                if (
                                    !event.defaultPrevented &&
                                    event.button === 0 &&
                                    (!target || target === "_self") &&
                                    !isModifiedEvent(event)
                                ) {
                                    event.preventDefault();
                                    navigate();
                                }
                            })
                        }
                    />
                );
            }}
        </RouterContext.Consumer>
    );
};

export default LinkButton;

This is basically copy-paste from https://github.com/ReactTraining/react-router/blob/b529499efcb906c814b9a3a68e2b4292a15b09c8/packages/react-router-dom/modules/Link.js

Was this page helpful?
0 / 5 - 0 ratings