Skip to content

Commit 21da316

Browse files
authored
Merge pull request #8156 from marmelab/sanitize-empty-values
[Doc] Fix tutorial about sanitizing empty values
2 parents bef5cc4 + 47b97c1 commit 21da316

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed

docs/Create.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,20 @@ The `transform` function can also return a `Promise`, which allows you to do all
338338

339339
**Tip**: If you want to have different transformations based on the button clicked by the user (e.g. if the creation form displays two submit buttons, one to "save", and another to "save and notify other admins"), you can set the `transform` prop on [the `<SaveButton>` component](./SaveButton.md), too.
340340

341-
**Tip**: A frequent usage of the `transform` function is to stripe empty string values returned by the form.
341+
## Cleaning Up Empty Strings
342+
343+
As a reminder, HTML form inputs always return strings, even for numbers and booleans. So the empty value for a text input is the empty string, not `null` or `undefined`. This means that the data sent to `dataProvider.create()` will contain empty strings:
344+
345+
```js
346+
{
347+
title: '',
348+
average_note: '',
349+
body: '',
350+
// etc.
351+
}
352+
```
353+
354+
If you prefer to have `null` values, or to omit the key for empty values, use [the `transform` prop](#transform) to sanitize the form data before submission:
342355

343356
```jsx
344357
export const UserCreate = (props) => {

docs/Edit.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,20 @@ export const UserEdit = (props) => {
539539
}
540540
```
541541

542-
**Tip**: A frequent usage of the `transform` function is to stripe empty string values returned by the form.
542+
## Cleaning Up Empty Strings
543+
544+
As a reminder, HTML form inputs always return strings, even for numbers and booleans. So the empty value for a text input is the empty string, not `null` or `undefined`. This means that the data sent to `dataProvider.update()` will contain empty strings:
545+
546+
```js
547+
{
548+
title: '',
549+
average_note: '',
550+
body: '',
551+
// etc.
552+
}
553+
```
554+
555+
If you prefer to have `null` values, or to omit the key for empty values, use [the `transform` prop](#transform) to sanitize the form data before submission:
543556

544557
```jsx
545558
export const UserEdit = (props) => {
@@ -559,6 +572,8 @@ export const UserEdit = (props) => {
559572
}
560573
```
561574

575+
As an alternative, you can clean up empty values at the input level, using [the `parse` prop](./Inputs.md#transforming-input-value-tofrom-record).
576+
562577
## Adding `meta` To The DataProvider Call
563578

564579
You can pass a custom `meta` to the `dataProvider` call, using either `queryOptions`, or `mutationOptions`:

docs/Inputs.md

+16-18
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,22 @@ Mnemonic for the two functions:
331331
- `parse()`: input -> record
332332
- `format()`: record -> input
333333

334-
Say the user would like to input values of 0-100 to a percentage field but your API (hence record) expects 0-1.0. You can use simple `parse()` and `format()` functions to archive the transform:
334+
A common usage for this feature is to strip empty strings from the record before saving it to the API. As a reminder, HTML form inputs always return strings, even for numbers and booleans. So the empty value for a text input is the empty string, not `null` or `undefined`. Leveraging `parse` allows you to transform the empty string to `null` before saving the record.
335+
336+
```jsx
337+
import { TextInput } from 'react-admin';
338+
339+
const TextInputWithNullEmptyValue = props => (
340+
<TextInput
341+
{...props}
342+
parse={ v => typeof v === 'string' && v.length === 0 ? null : v }
343+
/>
344+
);
345+
346+
export default TextInputWithNullEmptyValue;
347+
```
348+
349+
Let's look at another usage example. Say the user would like to input values of 0-100 to a percentage field but your API (hence record) expects 0-1.0. You can use simple `parse()` and `format()` functions to archive the transform:
335350

336351
```jsx
337352
<NumberInput source="percent" format={v => v * 100} parse={v => parseFloat(v) / 100} label="Formatted number" />
@@ -376,23 +391,6 @@ const dateParser = value => {
376391
<DateInput source="isodate" format={dateFormatter} parse={dateParser} defaultValue={new Date()} />
377392
```
378393

379-
**Tip:** To spread a conversion behaviour to your whole application, you can import a `react-admin`component then re-export them with their `format` and/or `parse` props set.
380-
381-
```jsx
382-
import * as React from 'react';
383-
import { TextInput } from 'react-admin';
384-
385-
const FilledOrNullTextInput = props => {
386-
return (
387-
<TextInput
388-
{...props}
389-
format={ v => typeof v === 'string' && v.length === 0 ? null : v }
390-
/>
391-
);
392-
};
393-
export default FilledOrNullTextInput;
394-
```
395-
396394
## Linking Two Inputs
397395

398396
Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former).

docs/SimpleForm.md

+37
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,43 @@ export const TagEdit = () => (
334334
);
335335
```
336336

337+
## Cleaning Up Empty Strings
338+
339+
As a reminder, HTML form inputs always return strings, even for numbers and booleans. So the empty value for a text input is the empty string, not `null` or `undefined`. This means that the data sent to the form handler will contain empty strings:
340+
341+
```js
342+
{
343+
title: '',
344+
average_note: '',
345+
body: '',
346+
// etc.
347+
}
348+
```
349+
350+
If you prefer to have `null` values, or to omit the key for empty values, use `transform` prop of the parent component ([`<Edit>`](./Edit.md#transform) or [`<Create>`](./Create.md#transform)) to sanitize the form data before passing it to the `dataProvider`:
351+
352+
```jsx
353+
export const UserEdit = (props) => {
354+
const transform = (data) => {
355+
const sanitizedData = {};
356+
for (const key in data) {
357+
if (typeof data[key] === "string" && data[key].trim().length === 0) continue;
358+
sanitizedData[key] = data[key];
359+
}
360+
return sanitizedData;
361+
};
362+
return (
363+
<Edit {...props} transform={transform}>
364+
<SimpleForm>
365+
...
366+
</SimpleForm>
367+
</Edit>
368+
);
369+
}
370+
```
371+
372+
As an alternative, you can clean up empty values at the input level, using [the `parse` prop](./Inputs.md#transforming-input-value-tofrom-record).
373+
337374
## Using Fields As Children
338375

339376
The basic usage of `<SimpleForm>` is to pass [Input components](./Inputs.md) as children. For non-editable fields, you can pass `disabled` inputs, or even [Field components](./Fields.md). But since `<Field>` components have no label by default, you'll have to wrap your inputs in a `<Labeled>` component in that case:

docs/TabbedForm.md

+35
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,41 @@ const ProductEdit = () => (
479479

480480
**Tip**: React-admin renders each tab *twice*: once to get the tab header, and once to get the tab content. If you use a custom component instead of a `<FormTab>`, make sure that it accepts an `intent` prop, and renders differently when the value of that prop is 'header' or 'content'.
481481

482+
## Cleaning Up Empty Strings
483+
484+
As a reminder, HTML form inputs always return strings, even for numbers and booleans. So the empty value for a text input is the empty string, not `null` or `undefined`. This means that the data sent to the form handler will contain empty strings:
485+
486+
```js
487+
{
488+
title: '',
489+
average_note: '',
490+
body: '',
491+
// etc.
492+
}
493+
```
494+
495+
If you prefer to have `null` values, or to omit the key for empty values, use `transform` prop of the parent component ([`<Edit>`](./Edit.md#transform) or [`<Create>`](./Create.md#transform)) to sanitize the form data before passing it to the `dataProvider`:
496+
497+
```jsx
498+
export const UserEdit = (props) => {
499+
const transform = (data) => {
500+
const sanitizedData = {};
501+
for (const key in data) {
502+
if (typeof data[key] === "string" && data[key].trim().length === 0) continue;
503+
sanitizedData[key] = data[key];
504+
}
505+
return sanitizedData;
506+
};
507+
return (
508+
<Edit {...props} transform={transform}>
509+
<TabbedForm>
510+
...
511+
</TabbedForm>
512+
</Edit>
513+
);
514+
}
515+
```
516+
482517
## Using Fields As FormTab Children
483518

484519
The basic usage of `<TabbedForm>` is to pass [Input components](./Inputs.md) as children of `<FormTab>`. For non-editable fields, you can pass `disabled` inputs, or even [Field components](./Fields.md). But since `<Field>` components have no label by default, you'll have to wrap your inputs in a `<Labeled>` component in that case:

0 commit comments

Comments
 (0)