From e5b28cbbb34fada46226619af5db812003f79809 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 22 Apr 2021 11:03:56 +0200 Subject: [PATCH] Update documentation for custom fields --- docs/Fields.md | 182 ++++++++++++++++++++++++++++--------------------- 1 file changed, 104 insertions(+), 78 deletions(-) diff --git a/docs/Fields.md b/docs/Fields.md index eb8688d218a..191342d98b7 100644 --- a/docs/Fields.md +++ b/docs/Fields.md @@ -746,8 +746,6 @@ TagsField.defaultProps = { ## Reference Fields - - ### `` `` is useful for displaying many-to-one and one-to-one relationships. This component fetches a referenced record (using the `dataProvider.getMany()` method), and passes it to its child. A `` displays nothing on its own, it just fetches the data and expects its child to render it. Usual child components for `` are other `` components. @@ -1364,6 +1362,85 @@ PriceField.defaultProps = { ``` {% endraw %} +## Writing Your Own Field Component + +If you don't find what you need in the list above, you can write your own Field component. It must be a regular React component, accepting a `source` attribute and retrieving the `record` from the `RecordContext` with the `useRecordContext` hook. React-admin will set the `record` in this context based on the API response data at render time. The field component only needs to find the `source` in the `record` and display it. + +For instance, here is an equivalent of react-admin's `` component: + +```jsx +import * as React from "react"; +import PropTypes from 'prop-types'; +import { useRecordContext } from 'react-admin'; + +const TextField = (props) => { + const { source } = props; + const record = useRecordContext(props); + return {record[source]}; +} + +TextField.propTypes = { + label: PropTypes.string, + record: PropTypes.object, + source: PropTypes.string.isRequired, +}; + +export default TextField; +``` + +**Tip**: The `label` attribute isn't used in the `render()` method, but react-admin uses it to display the table header. + +**Tip**: If you want to support deep field sources (e.g. source values like `author.name`), use [lodash/get](https://www.npmjs.com/package/lodash.get) to replace the simple object lookup: + +```jsx +import * as React from "react"; +import PropTypes from 'prop-types'; +import get from 'lodash/get'; +import { useRecordContext } from 'react-admin'; + +const TextField = (props) => { + const { source } = props; + const record = useRecordContext(props); + + return {get(record, source)}; +} +``` + +If you are not looking for reusability, you can create even simpler components, with no attributes. Let's say an API returns user records with `firstName` and `lastName` properties, and that you want to display a full name in a user list. + +```js +{ + id: 123, + firstName: 'John', + lastName: 'Doe' +} +``` + +The component will be: + +```jsx +import * as React from "react"; +import { List, Datagrid, TextField, useRecordContext } from 'react-admin'; + +const FullNameField = () => { + const record = useRecordContext(props); + + return {record.firstName} {record.lastName}; +} + +FullNameField.defaultProps = { label: 'Name' }; + +export const UserList = (props) => ( + + + + + +); +``` + +**Tip**: In such custom fields, the `source` is optional. React-admin uses it to determine which column to use for sorting when the column header is clicked. In case you use the `source` property for additional purposes, the sorting can be overridden by the `sortBy` property on any `Field` component. + ### Adding A Label To Custom Field Components When you use one of the react-admin `Field` components in an `Edit`, `Create` or `Show` view, react-admin includes a label on top of the field value, as in the following example: @@ -1373,11 +1450,14 @@ When you use one of the react-admin `Field` components in an `Edit`, `Create` or For your custom fields, however, the label doesn't appear by default. You need to opt in this feature by setting the `addLabel` prop to `true` in the `defaultProps`. ```diff -const FullNameField = ({ record = {} }) => ( - - {record.firstName} {record.lastName} - -); +const FullNameField = (props) => { + const record = useRecordContext(props); + return ( + + {record.firstName} {record.lastName} + + ); +} FullNameField.defaultProps = { label: 'Name', @@ -1392,10 +1472,10 @@ If you don't use any of these layouts, the `addLabel` trick won't work. You'll h ```jsx import { Labeled } from 'react-admin'; -const MyShowLayout = ({ record }) => ( +const MyShowLayout = () => (
- +
); @@ -1406,7 +1486,7 @@ You can also leverage the default label resolution mechanism by providing the `r ```jsx import { Labeled } from 'react-admin'; -const MyShowLayout = ({ record }) => ( +const MyShowLayout = () => (
@@ -1425,10 +1505,12 @@ For such cases, you can use the custom field approach: use the injected `record` import * as React from "react"; import { EmailField } from 'react-admin'; -const ConditionalEmailField = ({ record, ...rest }) => - record && record.hasEmail - ? +const ConditionalEmailField = (props) => { + const record = useRecordContext(props); + return record && record.hasEmail + ? : null; +} export default ConditionalEmailField; ``` @@ -1443,14 +1525,17 @@ One solution is to add the label manually in the custom component: import * as React from "react"; import { Labeled, EmailField } from 'react-admin'; -const ConditionalEmailField = ({ record, ...rest }) => - record && record.hasEmail +const ConditionalEmailField = (props) => { + const record = useRecordContext(props); + + return record && record.hasEmail ? ( - + ) : null; +} export default ConditionalEmailField; ``` @@ -1525,75 +1610,16 @@ const UserShow = props => ( And now you can use a regular Field component, and the label displays correctly in the Show view. -## Writing Your Own Field Component - -If you don't find what you need in the list above, you can write your own Field component. It must be a regular React component, accepting not only a `source` attribute, but also a `record` attribute. React-admin will inject the `record` based on the API response data at render time. The field component only needs to find the `source` in the `record` and display it. - -For instance, here is an equivalent of react-admin's `` component: - -```jsx -import * as React from "react"; -import PropTypes from 'prop-types'; - -const TextField = ({ source, record = {} }) => {record[source]}; - -TextField.propTypes = { - label: PropTypes.string, - record: PropTypes.object, - source: PropTypes.string.isRequired, -}; - -export default TextField; -``` - -**Tip**: The `label` attribute isn't used in the `render()` method, but react-admin uses it to display the table header. - -**Tip**: If you want to support deep field sources (e.g. source values like `author.name`), use [lodash/get](https://www.npmjs.com/package/lodash.get) to replace the simple object lookup: - -```jsx -import get from 'lodash/get'; -const TextField = ({ source, record = {} }) => {get(record, source)}; -``` - -If you are not looking for reusability, you can create even simpler components, with no attributes. Let's say an API returns user records with `firstName` and `lastName` properties, and that you want to display a full name in a user list. - -```js -{ - id: 123, - firstName: 'John', - lastName: 'Doe' -} -``` - -The component will be: - -```jsx -import * as React from "react"; -import { List, Datagrid, TextField } from 'react-admin'; - -const FullNameField = ({ record = {} }) => {record.firstName} {record.lastName}; -FullNameField.defaultProps = { label: 'Name' }; - -export const UserList = (props) => ( - - - - - -); -``` - -**Tip**: In such custom fields, the `source` is optional. React-admin uses it to determine which column to use for sorting when the column header is clicked. In case you use the `source` property for additional purposes, the sorting can be overridden by the `sortBy` property on any `Field` component. - ### Linking to other records Your custom Field component might need to display a link to another record. React Admin provides a `linkToRecord(basePath, id[, linkType])` method for this purpose. ```js -import { linkToRecord } from 'react-admin'; +import { linkToRecord, useRecordContext } from 'react-admin'; import { Link } from 'react-router-dom'; -const MyCustomField = ({ record: post }) => { +const MyCustomField = () => { + const post = useRecordContext(props); const linkToUser = linkToRecord('/users', post.user_id, 'show'); return {seller.username};