useInput
This hook lets you build custom inputs for react-admin. It’s a wrapper around react-hook-form’s useController
.
React-admin adds functionality to react-hook-form:
- handling of custom event emitters like
onChange
, - support for an array of validators,
- detection of required fields to add an asterisk to the field label,
- parse and format to translate record values to form values and vice-versa.
Usage
useInput
expects at least a source
, and returns an object with the following properties:
{ id, field, fieldState, formState, isRequired }
For instance, to build a custom input for a title
field:
import { useInput } from 'react-admin';
const TitleInput = ({ source, label }) => {
const { id, field, fieldState } = useInput({ source });
return (
<label htmlFor={id}>
{label}
<input id={id} {...field} />
{fieldState.error && <span>{fieldState.error.message}</span>}
</label>
);
};
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
source |
Required | string |
- | The name of the field in the record |
defaultValue |
Optional | any |
- | The default value of the input |
format |
Optional | Function |
- | A function to format the value from the record to the input value |
parse |
Optional | Function |
- | A function to parse the value from the input to the record value |
validate |
Optional | Function | Function[] |
- | A function or an array of functions to validate the input value |
id |
Optional | string |
- | The id of the input |
onChange |
Optional | Function |
- | A function to call when the input value changes |
onBlur |
Optional | Function |
- | A function to call when the input is blurred |
Additional props are passed to react-hook-form’s useController
hook.
Usage with Material UI <TextField>
// in LatLongInput.js
import TextField from '@mui/material/TextField';
import { useInput, required, InputHelperText } from 'react-admin';
const BoundedTextField = (props) => {
const { onChange, onBlur, label, helperText, ...rest } = props;
const {
field,
fieldState: { isTouched, invalid, error },
formState: { isSubmitted },
isRequired
} = useInput({
// Pass the event handlers to the hook but not the component as the field property already has them.
// useInput will call the provided onChange and onBlur in addition to the default needed by react-hook-form.
onChange,
onBlur,
...rest,
});
return (
<TextField
{...field}
label={label}
error={(isTouched || isSubmitted) && invalid}
helperText={helperText !== false || ((isTouched || isSubmitted) && invalid)
? (
<InputHelperText
touched={isTouched || isSubmitted}
error={error?.message}
helperText={helperText}
/>
)
: ''
}
required={isRequired}
{...rest}
/>
);
};
const LatLngInput = props => {
const { source, ...rest } = props;
return (
<span>
<BoundedTextField source="lat" label="Latitude" validate={required()} {...rest} />
<BoundedTextField source="lng" label="Longitude" validate={required()} {...rest} />
</span>
);
};
Usage with Material UI <Select>
// in SexInput.js
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import { useInput } from 'react-admin';
const SexInput = props => {
const {
field,
fieldState: { isTouched, invalid, error },
formState: { isSubmitted }
} = useInput(props);
return (
<Select
label="Sex"
{...field}
>
<MenuItem value="M">Male</MenuItem>
<MenuItem value="F">Female</MenuItem>
</Select>
);
};
export default SexInput;
Tip: useInput
accepts all arguments that you can pass to useController
. Besides, components using useInput
accept props like format
and parse
, to convert values from the form to the input, and vice-versa:
const parse = value => {/* ... */};
const format = value => {/* ... */};
const PersonEdit = () => (
<Edit>
<SimpleForm>
<SexInput
source="sex"
format={formValue => formValue === 0 ? 'M' : 'F'}
parse={inputValue => inputValue === 'M' ? 0 : 1}
/>
</SimpleForm>
</Edit>
);
Tip: Remember to use react-admin’s <InputHelperText>
component in custom inputs to properly translate and render messages and errors coming from useInput()
.
Important note about formState
react-hook-form’s formState
is wrapped with a Proxy to improve render performance and skip extra computation if specific state is not subscribed. So, make sure you deconstruct or read the formState
before render in order to enable the subscription.
const { isDirty } = useFormState(); // ✅
const formState = useFormState(); // ❌ should deconstruct the formState
This pattern should be followed when writing a custom input with useInput()
.
const { formState: { isSubmitted }} = useInput(props); // ✅
const { formState } = useInput(props);
const submitted = formState.isSubmitted; // ❌