Closed nacimgoura closed 6 years ago
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 used this solution: https://stackoverflow.com/a/53031676/6744320
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>
@zhouhao27 's solution fails for nested dictionaries, in that case you can use this:
import get from 'lodash/get';
const TextArrayField = ({ record, source }) => {
const array = get(record, source)
...
}
And usage:
<TextArrayField source="dict.tags">
<SingleFieldList>
<ChipField />
</SingleFieldList>
</TextArrayField>
@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>
good
might be late to the party here, but having a similar problem
as a design solution, is there any value in allowing source="."
inside arrays, which would use the entire current list element?
I'm not totally familiar with this codebase, but could maybe do it here where SingleFieldList creates its child
something like Object.assign({}, record, {'.': record})
, so the child can then ask for '.' field
If the json structure you use field fetches a one-to-many relationship and you want to bring them without the need for ArrayField, you can use the example mui table component code below.
import {useRecordContext} from 'react-admin'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; export default function ProductsInOrder() { const items = useRecordContext(); return ( <TableContainer component={Paper}> <Table sx={{ minWidth: 600 }} aria-label="simple table"> <TableHead> <TableRow> <TableCell>Id</TableCell> <TableCell>Name</TableCell> </TableRow> </TableHead> <TableBody> {items.line_items.map((item) => ( <TableRow> <TableCell>{item.id}</TableCell> <TableCell>{item.name}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> ); }
For future readers (myself included), I guess react-admin now has TextArrayField included in "@react-admin/ra-form-layout/lib/stories/TextArrayField" (i think in react admin enterprise), but I could not find this documented anywhere, so here you go.
Hello, I want to display a list of items that are stored in a array ([1,3,4,5,7...]) 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 :
But I think there is a better way. Thanks you for your help !