react hook form: as vs render: unable to understrand the syntax and how are they same - react-hook-form

I have seen in react hook forms using as and also render
Eg:
<Controller
render={({ field }) => <input {...field} />}
name="firstName"
control={control}
defaultValue=""
/>
or
<Controller
as={<input .. />}
name="firstName"
control={control}
defaultValue=""
/>
whats the difference

<Controller/> is good to use with there is an external controlled component such (e.g. Material-UI) to wrap the whole component and control is easier.
render is useful when you want to customise the external component while as is just renders the original component. An example of using render could be:
import { TextField } from '#mui/material';
import { Controller } from 'react-hook-form';
const FormTextField = ({
label,
name,
...props
}) => {
return (
<Controller
name={name}
render={({ field, fieldState: { error } }) => (
<TextField
{...field}
label={label}
error={!!error}
helperText={error ? error?.message : ''}
/>
)}
{...props}
/>
);
};
As you can see, render gives you ability to access different values (such as error) in the Material UI component which is not easy to do with as.
Read more about what properties you have access in render at https://react-hook-form.com/api/usecontroller/controller
This example is also helpful: https://codesandbox.io/s/react-hook-form-v7-controller-5h1q5

Related

React Final Form blurring each field

I'm using Final Form arrays to dynamically render custom fields that consist of two text fields, both of which are validated.
I'm not sure how to go about blurring the fields individually. This is what I currently have:
<FieldArray name="customField">
{({ fields }) => {
return fields.map((name, index) => {
return (
<Field name={name}>
{(props) => {
<CustomInput
onBlur={props.input.onBlur}
}
</Field>
And then in CustomInput:
<>
<TextField name='first' onBlur={onBlur} />
<TextField name='second' onBlur={onBlur} />
</>
The problem with this approach is that whenever the first TextField is blurred, both of them get validated (instead of just the first one).
Is something analogous to this
<TextField name='first' onBlur={e => onBlur(e, 'first')} />
<TextField name='first' onBlur={e => onBlur(e, 'second')} />
possible?

react-hook-form: Failed to set the 'value' property on 'HTMLInputElement'

I am trying to use react hook form for user input to upload file
import "./styles.css";
import { useForm, Controller } from "react-hook-form";
import {
Col,
Row,
Form,
FormGroup,
InputGroup,
Input,
Container,
Button
} from "reactstrap";
export default function App() {
const onSubmit = (data) => {
console.log(data);
};
const { control, handleSubmit } = useForm();
return (
<Container>
<Form onSubmit={handleSubmit(onSubmit)}>
<Row className="m-3">
<Col>
<FormGroup row className="mr-md-1">
<InputGroup className="mb-3">
<Controller
name="itemlist2"
control={control}
render={({ field: { ref, ...field } }) => (
<Input
{...field}
type="file"
required
innerRef={ref}
onChange={(e) => {
field.onChange(e.target.files);
}}
/>
)}
/>
</InputGroup>
</FormGroup>
</Col>
</Row>
<Button color="primary" className="mr-1">
{"Save Changes"}
</Button>
</Form>
</Container>
);
}
Check on https://codesandbox.io/s/affectionate-moon-dmn8q
I get
I'm not very familiar with reactstrap, but i think you have to omit the value prop which is part of field. You can’t set the
value of an input with type="file". Check this answer for more infos.
<Controller
name="itemlist2"
control={control}
render={({ field: { value, ...field } }) => (
<Input
{...field}
type="file"
required
innerRef={field.ref}
onChange={(e) => {
field.onChange(e.target.files);
}}
/>
)}
/>
The issue is due to the fact that you are using a controlled input and not passing value to render it out on onSubmit.
Use { field: { value, ...field } } as props to the render method and it will set the 'value' property on 'HTMLInputElement' otherwise it will only pass an empty string which is in conflict with a file type data.
It should be like this <input type="file" value="c:/js.txt"/> but you are doing like this <input type="file"/>
Complete Code:
import "./styles.css";
import { useForm, Controller } from "react-hook-form";
import {
Col,
Row,
Form,
FormGroup,
InputGroup,
Input,
Container,
Button
} from "reactstrap";
export default function App() {
const onSubmit = (data) => {
console.log(data);
};
const { control, handleSubmit } = useForm();
return (
<Container>
<Form onSubmit={handleSubmit(onSubmit)}>
<Row className="m-3">
<Col>
<FormGroup row className="mr-md-1">
<InputGroup className="mb-3">
<Controller
name="itemlist2"
control={control}
render={({ field: { value, ...field } }) => (
<Input
{...field}
type="file"
required
innerRef={field.ref}
onChange={(e) => {
field.onChange(e.target.files);
}}
/>
)}
/>
</InputGroup>
</FormGroup>
</Col>
</Row>
<Button color="primary" className="mr-1">
{"Save Changes"}
</Button>
</Form>
</Container>
);
}

react-hook-form and react-datetime: How to set the time to moment() from a button

I am using react-datetime inside a react-hook-form
I want the user to easily set the time to current time using a button Immediate. Instead of selecting the current time manually.
I am trying the below
const [currentDateTime, setcurrentDateTime] = useState(null);
<Controller
name="resetDateTime"
control={control}
required
render={({ field }) => (
<Datetime
onChange={setcurrentDateTime}
inputProps={{
placeholder: "MM-DD-YYYY HH:mm",
}}
value={currentDateTime}
viewMode="time"
/>
)}
/>
<Button color="primary" className="ml-1" onClick={() => setcurrentDateTime(moment())}>
{"Immediate"}
</Button>
The problem is onSubmit the react-hook-form I get resetDateTime = undefined
How to implement this properly. So I can use the Immediate button and also submit form and get the resetDateTime value
You're mixing RHF with your local state currentDateTime and are not linking the. field to RHF as you're missing to spread the field object to your <Datetime /> component.
The correct way would be to use RHF's setValue to update your resetDateTime field and get rid of the useState hook.
const { control, handleSubmit, setValue } = useForm();
<Controller
name="resetDateTime"
control={control}
required
render={({ field }) => (
<Datetime
{...field}
inputProps={{
placeholder: "MM-DD-YYYY HH:mm",
}}
viewMode="time"
/>
)}
/>
<Button color="primary" className="ml-1" onClick={() => setValue("resetDateTime", moment())}>
{"Immediate"}
</Button>

Migration from v6 to v7

I have upgraded to v7 of react-hook-form today. And all went fine until i came across some legacy code using ref attribute.
In version 6 this worked perfectly
<ToggleSwitch toggleName='ifMonitoring'
ref={(e) => {
monitoring.current = e;
register(e);
}}
/>
But in version 7 ref is not used anymore, instead its {...register('ifMonitoring')}. This works fine accross the application but the above example is only one which doesnt work.
I have tried to search for similar issues but to no avail.
Anyone can help?
EDIT:
Adding more code to better understand this
function Edit() => {
const monitoring = useRef(null);
return <Controller name='monitoring' control={control} render={({ field: { ref }, fieldState }) => <ToggleSwitch ref={ref} checked={portInfo.isMonitored} />} />
ToggleSwitch is component with its own state. It does have onChange but to maintain its state
const ToggleSwitch = forwardRef((props, ref) => {
const [toggleCheck, setToggleCheck] = useState(props.checked);
const handleOnChange = (e) => {
setToggleCheck((prevState) => !prevState);
if (props.onChange) {
props.onChange(props.entry._id);
}
};
return (
<div className={`toggle btn btn-sm`}>
<input type='checkbox' defaultChecked={toggleCheck} onChange={handleOnChange} ref={ref} name={`toggle`} />
<div className='toggle-group'>
<label htmlFor={`toggle`} className={`btn btn-success`}>
In Use
</label>
<label htmlFor={`toggle`} className={`btn btn-danger`}>
Not in Use
</label>
<span className={`toggle-handle btn btn-light btn-xs`}></span>
</div>
</div>
);
EDIT 2&3:
Not working v7 Codesandbox
Working v6 Codesandbox
The ref is actually still there, it's returned by register among other things that we spread.
There is an example on the React Hook Form documentation to share ref usage.
You can do like this:
const { ref, ...rest } = register("ifMonitoring");
<ToggleSwitch
{...rest}
ref={(e) => {
monitoring.current = e;
ref(e);
}}
/>;
Edit
In your specific case, you are passing the ...rest to your ToggleSwitch component, but the component does not forward these props to the inner input (except the name that you pass yourself in a prop).
The problem here comes especially from onChange that is part of the elements inside your rest variable. As you have also your custom onChange, you can combine both yours and the one of React Hook Form.
So, in your main component you can pass the rest props like this:
<ToggleSwitch
inputProps={rest}
ref={(e) => {
ref(e);
inUseRef.current = e;
}}
// [...]
/>
And in your ToggleSwitch component, you can call the onChange of RHF in your own function, pass the input props to the input, and pass your onChange function after.
const handleOnChange = (e) => {
setToggleCheck((prevState) => !prevState);
if (inputProps.onChange) {
inputProps.onChange(e);
}
};
// [...]
<input
// [...]
{...inputProps}
onChange={handleOnChange}
ref={ref}
// [...]
/>
Here is the codesandbox.

Unable to access mutator functions in Wizard form page while using react-final-form

I am trying to create a Wizard form using react-final-form by referring to this code https://codesandbox.io/s/km2n35kq3v. For my use case I need some mutator functions to be used inside my form fields. This example illustrates how to do that - https://codesandbox.io/s/kx8qv67nk5?from-embed.
I am not sure how to access mutator functions in my form steps when I am using a wizard form instead of a single page form.
I tried to combine both the examples by modifying the <Form> component rendered by Wizard.js to pass in the mutators. However I cannot access these mutators in the Wizard form pages.
In Wizard.js
return (
<Form
mutators={{
// potentially other mutators could be merged here
...arrayMutators,
}}
render={({
handleSubmit,
submitting,
values,
pristine,
invalid,
form: {
mutators: {push, pop, remove},
},
}) => {
return (
<form onSubmit={handleSubmit}>
Another file index.js
<Wizard
initialValues={{ employed: true, stooge: "larry" }}
onSubmit={onSubmit}
>
<Wizard.Page>
<FieldArray name="customers">
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component="input"
placeholder="First Name"
/>
<span
onClick={() => fields.remove(index)}
style={{ cursor: "pointer" }}
>
❌
</span>
</div>
))
}
</FieldArray>
</Wizard.Page>
</Wizard>
It errors out - remove is undefined in index.js
Look at this working example: https://codesandbox.io/s/znzlqvzvnx
changes I have made:
Wizard.js
static Page = ({ children, mutators }) => {
if(typeof children === 'function'){
return children(mutators);
}
return children;
};
...
<form onSubmit={handleSubmit}>
{
// activePage
<activePage.type {...activePage.props} mutators={mutators} />
}
...
index.js (only first <Wizard.page>)
<Wizard.Page>
{
({ upper }) => (
<React.Fragment>
<div>
<label>First Name</label>
<Field
name="firstName"
component="input"
...
</div>
</React.Fragment>
)
}
</Wizard.Page>

Resources