React-admin: LinkField similar to EditButton or ShowButton

Created on 21 Aug 2018  路  3Comments  路  Source: marmelab/react-admin

Hello and thanks for this project :-).

In my list components, I would prefer to be able to click on the id or name of my record instead of the edit/show button to access the detailed page of the record.

For example here I always click on the class name instead of the (far on the right) edit button to see details about the class.

image

So I have been looking for a kind of LinkField where the text is specified with the source prop and the link is a function with the record as parameter (but it could also be a linkType props as in ReferenceField.

<LinkField source="name" linkType="edit" />

or

<LinkField source="name" link={record => `/someResource/${record.id}`} />

Does it make sense to you?

Most helpful comment

Here it is and how you may use it. I don't have time for documentation, so you'll have to follow the render logic to see what the props do.

<LinkAnyFieldButton to="show">
    <ChipField source="title" />
</LinkAnyFieldButton>
// react etc
import React, { Component, Children, cloneElement, Fragment } from 'react';
import PropTypes from 'prop-types';
import shouldUpdate from 'recompose/shouldUpdate';
import { withRouter } from 'react-router-dom';
import compose from 'recompose/compose';
// mui etc
import { withStyles } from '@material-ui/core/styles';
// ra etc
import { Link, linkToRecord } from 'react-admin';

const styles = {
    link: {
        cursor: 'pointer',
        '& *': {
            cursor: 'pointer',
        },
    },
};

const sanitizeRestClasses = ({ link, ...rest }) => rest;
const sanitizeRestProps = ({
    classes,
    to,
    relative,
    disabled,
    history,
    location,
    match,
    staticContext,
    ...rest
}) => rest;

class LinkAnyFieldButton extends Component {
    static propTypes = {
        // passed by parent
        basePath: PropTypes.string,
        children: PropTypes.any,
        record: PropTypes.object,
        classes: PropTypes.object,
        // own props
        to: PropTypes.string,
        relative: PropTypes.bool,
        disabled: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
        // withRouter props
        match: PropTypes.object,
    };
    static defaultProps = {
        to: 'show',
    };

    render() {
        const {
            basePath = '',
            children,
            record = {},
            classes,
            to,
            relative,
            disabled,
            match,
        } = this.props;

        const recordLink = to.startsWith('/')
            ? to.replace(/\/:([\w-]+)/g, (m, param) => {
                  return `/${record[param]}`;
              })
            : linkToRecord(basePath, record.id, to);
        const completeTo = relative ? `${match.url}${recordLink}` : recordLink;
        const rest = sanitizeRestProps(this.props);
        const restClasses = sanitizeRestClasses(classes);
        const isDisabled =
            typeof disabled === 'function' ? disabled(record) : disabled;

        const countChildren = Children.count(children);

        const childElements =
            countChildren === 1
                ? cloneElement(children, {
                      record,
                      basePath,
                      classes: restClasses,
                  })
                : Children.map(children, field =>
                      cloneElement(field, {
                          record,
                          basePath,
                          classes: restClasses,
                          ...rest,
                      })
                  );

        return isDisabled ? (
            <Fragment>{childElements}</Fragment>
        ) : (
            <Link to={`${completeTo}`} className={classes.link}>
                {childElements}
            </Link>
        );
    }
}

const enhance = compose(
    withRouter, // adds props: history, location, match, staticContext
    withStyles(styles),
    shouldUpdate(
        (props, nextProps) =>
            props.translate !== nextProps.translate ||
            (props.record &&
                nextProps.record &&
                props.record.id !== nextProps.record.id) ||
            props.basePath !== nextProps.basePath ||
            (props.record == null && nextProps.record != null)
    )
);

export default enhance(LinkAnyFieldButton);

All 3 comments

Makes sense to me. In the last month, I've made a custom component called LinkAnyFieldButton that I can wrap around any field and make it link like a ShowButton or EditButton and more.

Here it is and how you may use it. I don't have time for documentation, so you'll have to follow the render logic to see what the props do.

<LinkAnyFieldButton to="show">
    <ChipField source="title" />
</LinkAnyFieldButton>
// react etc
import React, { Component, Children, cloneElement, Fragment } from 'react';
import PropTypes from 'prop-types';
import shouldUpdate from 'recompose/shouldUpdate';
import { withRouter } from 'react-router-dom';
import compose from 'recompose/compose';
// mui etc
import { withStyles } from '@material-ui/core/styles';
// ra etc
import { Link, linkToRecord } from 'react-admin';

const styles = {
    link: {
        cursor: 'pointer',
        '& *': {
            cursor: 'pointer',
        },
    },
};

const sanitizeRestClasses = ({ link, ...rest }) => rest;
const sanitizeRestProps = ({
    classes,
    to,
    relative,
    disabled,
    history,
    location,
    match,
    staticContext,
    ...rest
}) => rest;

class LinkAnyFieldButton extends Component {
    static propTypes = {
        // passed by parent
        basePath: PropTypes.string,
        children: PropTypes.any,
        record: PropTypes.object,
        classes: PropTypes.object,
        // own props
        to: PropTypes.string,
        relative: PropTypes.bool,
        disabled: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
        // withRouter props
        match: PropTypes.object,
    };
    static defaultProps = {
        to: 'show',
    };

    render() {
        const {
            basePath = '',
            children,
            record = {},
            classes,
            to,
            relative,
            disabled,
            match,
        } = this.props;

        const recordLink = to.startsWith('/')
            ? to.replace(/\/:([\w-]+)/g, (m, param) => {
                  return `/${record[param]}`;
              })
            : linkToRecord(basePath, record.id, to);
        const completeTo = relative ? `${match.url}${recordLink}` : recordLink;
        const rest = sanitizeRestProps(this.props);
        const restClasses = sanitizeRestClasses(classes);
        const isDisabled =
            typeof disabled === 'function' ? disabled(record) : disabled;

        const countChildren = Children.count(children);

        const childElements =
            countChildren === 1
                ? cloneElement(children, {
                      record,
                      basePath,
                      classes: restClasses,
                  })
                : Children.map(children, field =>
                      cloneElement(field, {
                          record,
                          basePath,
                          classes: restClasses,
                          ...rest,
                      })
                  );

        return isDisabled ? (
            <Fragment>{childElements}</Fragment>
        ) : (
            <Link to={`${completeTo}`} className={classes.link}>
                {childElements}
            </Link>
        );
    }
}

const enhance = compose(
    withRouter, // adds props: history, location, match, staticContext
    withStyles(styles),
    shouldUpdate(
        (props, nextProps) =>
            props.translate !== nextProps.translate ||
            (props.record &&
                nextProps.record &&
                props.record.id !== nextProps.record.id) ||
            props.basePath !== nextProps.basePath ||
            (props.record == null && nextProps.record != null)
    )
);

export default enhance(LinkAnyFieldButton);

Thanks for the suggestion! As this can be achieved in userland (thank you for the example @christiaanwesterbeek :heart:), we won't make it a priority. If you feel this can be useful for others, please create an addon for it and add it to our Ecosystem documentation page.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kopax picture kopax  路  3Comments

aserrallerios picture aserrallerios  路  3Comments

Kmaschta picture Kmaschta  路  3Comments

pixelscripter picture pixelscripter  路  3Comments

9747749366 picture 9747749366  路  3Comments