React-admin: display array with arrayField or other

Created on 3 Oct 2018  Â·  15Comments  Â·  Source: marmelab/react-admin

Hello,
I want to display a list of items that are stored in a array ([1,3,4,5,7...])
capture d ecran 2018-10-03 a 15 47 46
With ArrayField I can't display the elements as I want because it expects an object array when I have a simple array.
How to display the elements stored in a array ?
Currently I am creating my own field for this :

const TagsFieldInstitutes = ({ record }) => (
  <div>
    {record.institutes.map(item => (
      <span key={item} className="chip">
        {item}
      </span>
    ))}
  </div>
);

But I think there is a better way.
Thanks you for your help !

Most helpful comment

I cannot comprehend why this has been essentially wontfixed. There is nothing at all strange about an API returning an array of strings like ['a', 'b', 'c']. And it's ridiculous to expect the API to be rewritten to return [{label: 'a'}, {label: 'b'}, {label: 'c'}]. That changes the API response that other clients uses from a sensible response to a nonsensical one.

All 15 comments

From my experience it takes less time to wrap the value server side in an object, is this an option?
You could make your own chipfield

import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash/get';
import pure from 'recompose/pure';
import Chip from '@material-ui/core/Chip';
import { withStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import sanitizeRestProps from './sanitizeRestProps';

const styles = {
    chip: { margin: 4 },
};

export const ChipField = ({
    className,
    classes = {},
    source,
    record = {},
    ...rest
}) => {
    return (
        <Chip
            className={classnames(classes.chip, className)}
            label={source !== null && source !== undefined ? get(record, source) : record}
            {...sanitizeRestProps(rest)}
        />
    );
};

ChipField.propTypes = {
    className: PropTypes.string,
    classes: PropTypes.object,
    elStyle: PropTypes.object,
    sortBy: PropTypes.string,
    source: PropTypes.string,
    record: PropTypes.object,
};

const PureChipField = withStyles(styles)(pure(ChipField));

export default PureChipField;

Afterwards you can do something like

<ArrayField source="institutes">
    <SingleFieldList>
        <ChipField />
    </SingleFieldList>
</ArrayField>

(this code was not tested)

Hi, and thanks for your question. As explained in the react-admin contributing guide, the right place to ask a "How To" question, get usage advice, or troubleshoot your own code, is StackOverFlow.

This makes your question easy to find by the core team, and the developer community. Unlike Github, StackOverFlow has great SEO, gamification, voting, and reputation. That's why we chose it, and decided to keep GitHub issues only for bugs and feature requests.

So I'm closing this issue, and inviting you to ask your question at:

http://stackoverflow.com/questions/tagged/react-admin

And once you get a response, please continue to hang out on the react-admin channel in StackOverflow. That way, you can help newcomers and share your expertise!

@djhi I also had this problem and I lost some time doing conversions in the data provider (all 4 CRUD types need to be converted, otherwise the app gives errors when trying to show/update/delete).

It would be a good feature if both ArrayField and ArrayInput (along with its "siblings") supported the plain Array structure too: [1,2,3,4,5]

Could we mark this as a possible enhancement please? (I can create a new issue if you wish)

P.S. I could not find any related stack-overflow question/answer related to this.

@afilp as explained in the documentation:

If you need to render a collection in a custom way, it's often simpler to write your own component:

const TagsField = ({ record }) => (
    <ul>
        {record.tags.map(item => (
            <li key={item.name}>{item.name}</li>
        ))}
    </ul>
)
TagsField.defaultProps = { addLabel: true };

We won't add a new field to replace a one liner in userland.

@fzaninotto If I may add, it was not a one liner because I had to make changes also for the "C"reate and "U"pdate, because their logic also require an array of objects (instead of a simple array), in their respective ArrayInput. I did not propose to add a new field.

I could put some little effort to create a PR for ArrayField and ArrayInput that also work for arrays with "non-object values", would you care for this? Thanks.

I ended with a more generic solution:

import { cloneElement } from 'react'

export const StringToLabelObject = ({ record, children, ...rest }) =>
  cloneElement(children, {
    record: { label: record },
    ...rest,
  })
<ArrayField source="institutes">
  <SingleFieldList>
    <StringToLabelObject>
      <ChipField source="label"/>
    </StringToLabelObject>
  </SingleFieldList>
</ArrayField>

but I agree this is quite a common case and should be supported by react-admin.

@paradoxxxzero @danlupascu How do you handle the Create and Update that use the respective input fields?

@afilp This is how I did it:

export const UsersEdit = props => (
    <Edit {...props}>
      <SimpleForm>
        <TextInput source="email" />
        <BooleanInput source="isVerified" />
        <TextInput source="verifyToken" />
        <ArrayInput source="roles"><SimpleFormIterator><TextInput/></SimpleFormIterator></ArrayInput>
      </SimpleForm>
    </Edit>
);

In my case I have a field 'roles' that is an array of roles (strings) that the user has.

I cannot comprehend why this has been essentially wontfixed. There is nothing at all strange about an API returning an array of strings like ['a', 'b', 'c']. And it's ridiculous to expect the API to be rewritten to return [{label: 'a'}, {label: 'b'}, {label: 'c'}]. That changes the API response that other clients uses from a sensible response to a nonsensical one.

I would add that while the Documentation's assumption is that this is just a simple one line solution, the "complexity" of this issue was that I couldn't believe there was not a way to do it within the react admin ecosystem. It took a while to find this issue and be convinced that the React Admin team really does not think that an array ['cat', 'dog', 'man', 'bear'] deserves native support

This is a very surprising design choice IMO and that surprise should be considered when designing the API

Let me reiterate: react-admin doesn't require that you change your API to return an array of objects instead of an array of strings.

For fields, the zero-documentation, pure react way of doing it is documented and simple enough:

const TextArrayField = ({ record, source }) => (
    <>
        {record[source].map(item => <Chip label={item} key={item} />)}
    </>
)
TextArrayField.defaultProps = { addLabel: true };

This must be an additional component, as the current ArrayField is designed to receive an iterator as child. So if we integrated this in the core, we'd have to explain the difference between ArrayField and TextArrayField. It would take you more time to learn about that than to write the component by hand, as above.

For inputs, the solution using <SimpleFormIterator> is also documented and simple enough.

<ArrayInput source="roles">
  <SimpleFormIterator>
    <TextInput/>
  </SimpleFormIterator>
</ArrayInput>      

So it all boils down to: Do you prefer to use react-admin for everything, or are you willing to use React whenever you can? We designed react-admin for the latter case, because we had many, many bad experiences with "frameworks" that force you to re-learn how to code.

We prefer spending our time implementing customer requirements rather than learning tools that partially implement our customer requirements, and that we'll maybe never be able to tweak completely.

I can definitely say that when I had this issue the documentation was not clear enough on what I had to do. I did not find the solution until after piles of source code digging and finding this bug report. If the "documentation" has anything to do with the "If you need to render a collection in a custom way, it's often simpler to write your own component" text – trying to get a chip field react-admin already has to use the array item instead of a property on the array item has nothing to do with "rendering a collection in a custom way" from the user's perspective.

I can also say that when I was reading source code and setting debug breakpoints and got to this line of the ChipField implementation I was incredibly annoyed to see that the component already had exactly the data it needed in record and all it needed was to not get a property from it and there was absolutely nothing I could pass to source to tell it not to read a property off the record. I had to add a multi-line hack to the project because a single line of code in react-admin made the component useless.

@fzaninotto Modified your code a bit. It works now.

import Chip from '@material-ui/core/Chip'

const TextArrayField = ({ record, source }) => {
  const array = record[source]
  if (typeof array === 'undefined' || array === null || array.length === 0) {
    return <div/>
  } else {
    return (
      <>
        {array.map(item => <Chip label={item} key={item}/>)}
      </>
    )    
  }
}
TextArrayField.defaultProps = { addLabel: true }

Usage:

      <TextArrayField source="tags">
        <SingleFieldList>
          <ChipField source="id" />
        </SingleFieldList>
      </TextArrayField>

For the input field I use

  • format={} to prevent default value as [Object]
  • label="" to prevent tags[0] tags[1] to show up in the input
<ArrayInput source="tags" helperText="for example, barcode">
            <SimpleFormIterator>
              <TextInput format={(value) => (typeof value === 'object' ? '' : value)} label="" />
            </SimpleFormIterator>
          </ArrayInput>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ericwb picture ericwb  Â·  3Comments

nicgirault picture nicgirault  Â·  3Comments

ilaif picture ilaif  Â·  3Comments

Dragomir-Ivanov picture Dragomir-Ivanov  Â·  3Comments

kikill95 picture kikill95  Â·  3Comments