react-hook-form access to validation rules in nested component - reactjs

I'd like to access to the validation rules defined in react-hook-form's Resister in the nested component to dynamically display required indicator(*).
Is there a way to access from Nested component?
I don't want to repeat by passing as a prop.
<TextBox ref={register({ required: true })} label="Serial No" name="serialNo" />
const TextBox = React.forwardRef(({ label, name }, ref) => (
<>
<label htmlFor={name}>
{label} {***required*** && <span>*</span>}
</label>
<input type="text" name={name} id={name} ref={ref} />
</>
))

have a look at https://react-hook-form.com/api#useFormContext
import React from "react";
import { useForm, FormProvider, useFormContext } from "react-hook-form";
export default function App() {
const methods = useForm();
const onSubmit = data => console.log(data);
return (
<FormProvider {...methods} > // pass all methods into the context
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NestedInput />
<input type="submit" />
</form>
</FormProvider>
);
}
function NestedInput() {
const { register } = useFormContext(); // retrieve all hook methods
return <input name="test" ref={register} />;
}
which allow you to access all hook form methods in the nested component level.

Related

Why does adding a ref in a `react-hook-form` input element prevent the form from rendering?

I'm new in the React world, and some people recommended that I use react-hook-form for my forms. I don't know if it's relevant to mention, but I use Tailwind CSS. Here, is my problem:
When I'm parsing ref = {register} at the end of an input nothing renders.
Without ref = {register} everything works perfectly. If someone can help, I'd be delighted.
Here is my code :
import './App.css';
import React from "react";
import { useForm } from "react-hook-form";
function App() {
const {register, handleSubmit} = useForm();
const onSubmit = data => console.log(data);
return (
< form onSubmit={handleSubmit(onSubmit)}>
<input type='text' placeholder='Email' name='email' ref = {register} />
<input type='password' placeholder='Password' name='password' ref = {register}/>
<input type='submit'/>
</form>
);
}
export default App;
You don't have to use a ref, and besides register is a function, not a ref. With latest version of React Hook Form (current is 7.34.0) you have to pass register as {...register("name_of_the_field")}.
function App() {
const {register, handleSubmit} = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type='text' placeholder='Email' name='email' {...register('email')} />
<input type='password' placeholder='Password' name='password' {...register('password')}/>
<input type='submit'/>
</form>
);
}

How to pass onChange and props in a function

I am trying to change the id of my input using props but I am also using an onChange event.
import React from "react";
function Toggle({ onChange }) {
return (
<>
<input type="checkbox" id="switch" onChange={onChange} />
<label htmlFor="switch"></label>
</>
);
}
export default Toggle;
I have tried like this:
import React from "react";
function Toggle({ onChange }, props) {
return (
<>
<input type="checkbox" id={props.id} onChange={onChange} />
<label htmlFor={props.id}>
</label>
</>
);
}
export default Toggle;
How do I do this?
You are destructuring props, you can obtain the other values like this:
{ onChange, ...props }
Your attempt
({ onChange }, props)
Is destructuring the first argument, and assuming that there is a second one called props
The ...props pattern from above is called a "rest" pattern, it also works with arrays
You can destructure id also.
function Toggle({ onChange, id }) {
return (
<>
<input type="checkbox" id={id} onChange={onChange} />
<label htmlFor={id}>
</label>
</>
);
}

Console Log from Props

I'm confused as I have managed to get my data to be logged via different means, but confused as to why when I use props for the data (rather than repeating code) it will not log the input.
For reference, I have a field component that will take props to drive what my react-hook-form TextField will request. I'd like to expand on the component but until it logs my data, I cannot proceed!
Below is the code that actually logs my data:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Button } from "#material-ui/core/";
const NewRequest = () => {
const { register, handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name='firstName'
render={({ field: { onChange, onBlur, value, name, ref } }) => (
<TextField
label='First Name'
variant='filled'
size='small'
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</form>
</div>
);
};
export default NewRequest;
I have then moved the Controller, TextField to create a component:
import React from "react";
import { Controller, useForm } from "react-hook-form";
import { TextField } from "#material-ui/core/";
const TextFieldComponent = (props) => {
const { name, label, size, variant } = props;
const { control } = useForm();
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};
export default TextFieldComponent;
Which I am using inside of another component (to generate a full form) and passing through my props (I will make a different component for Button, but for now it is where it is):
import React from "react";
import { useForm, Controller } from "react-hook-form";
import TextFieldComponent from "./form-components/text-field";
import { Button } from "#material-ui/core/";
const NewRequest= () => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
export default NewRequest;
Now pushing that component into an index.js file to render a form:
import React from "react";
import NewVendorForm from "../components/new-vendor-request";
import { useForm } from "react-hook-form";
const Home = () => {
const { handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm />
</form>
);
};
export default Home;
I'm stumped as to why this way would
a) customise my TextField in my form as intended
b) but not log my data as requested
I'm sure there is a very valid, basic reason as to why and it is my lack of understanding of console logging, but am in need of help to resolve!
Many thanks in advance.
The issue is that, in the refactored code, you're calling useForm twice, each of which generates a different control and data. You probably want to call useForm at the top level only, and pass in whatever you need (in particular control) to the form fields.
const Home = () => {
const { handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm control={control} />
</form>
);
};
const NewRequest= ({control}) => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
control={control}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
const TextFieldComponent = (props) => {
const { name, label, size, variant, control } = props;
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};

react-hook -form with SliderComponent input

Im new to react and I am trying to use a SliderComponent in a react Hook form but I cant seem able to fully understand how Controller works.
Here is my SliderComponent using react-input-slider:
export default function SliderComponent(props) {
const { axis, xmax, xmin, xstep, onChange } = props;;
return (
<div>
<Slider
axis={axis}
x={value.x}
xmax={xmax}
xmin={xmin}
xstep={xstep}
onChange={onChange}
/>
</div>
);
}
And here is my form:
export default function BiometricForm() {
const { handleSubmit, control } = useForm();
return (
<div className="registerForm_Container">
<form onSubmit={handleSubmit(onsubmit)}>
<Controller
control={control}
name="test"
render={({ props: { x, axis, xmax, xmin, xstep } }) => (
<SliderComponent
axis={"x"}
xmax={100}
xmin={1}
xstep={1}
value={x}
onChange={(e) => x.onChange(parseInt(e.target.value))}
/>
)}
/>
<button className="registerForm_Container_button" type="submit">
Register
</button>
</form>
</div>
);
}
I think it might be something to do with useState and that I am not able to reach useState of component. I have read that maybe its not necessary , any help? Thank you!
You are absolutely right, if you use RHF you don't need useState because RHF handles the form state for you. The important thing here is to pass an onChange handler to your <SliderComponent />, so that RHF can watch for value changes of your <SliderComponent /> and update the form state. You should also provide a defaultValue for the field, if you don't want it to be undefined if the user doesn't change the slider before submitting.
Also without the useState inside <SliderComponent />, you can also omit the <SliderComponent /> and just use <Slider />.
function BiometricForm() {
const { handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<div className="registerForm_Container">
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="test"
defaultValue={50}
render={({ field: { value, onChange } }) => (
<Slider
axis={"x"}
xmax={100}
xmin={1}
xstep={1}
onChange={({ x }) => onChange(x)}
x={value}
/>
)}
/>
<input type="submit" />
</form>
</div>
);
}

Input react wrapper

I want to make a form for user data and I was asked to make a wrapper react components for each type of input. My problem is passing the handler to the input component.
This is my form:
import React from 'react';
import {addUser as addNewUser} from '../../services/index';
import {TextBox} from './TextBox'
interface AddUserProps{
}
interface AddUserState{
UserName:string,
}
export default class AddUser extends React.Component<AddUserProps, AddUserState> {
constructor(props:any) {
super(props);
this.state = {
UserName: '',
};
this.updateUserName = this.updateUserName.bind(this);
}
updateUserName(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({UserName: event.target.value});
}
handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(this.state)
addNewUser(this.state)
}
render() {
return (
<div>
<form
className = "user-add-form"
onSubmit={(event) => this.handleSubmit(event)}
>
<h3>Adauga utilizatori:</h3>
<label htmlFor="userName">User Name:</label>
<TextBox name="userName" type="text" value={userName} handleChange={this.updateUserName}/>
<input type="submit" value="Add user"/>
</form>
</div>
)
}
}
export {AddUser};
And this is the input component that I made:
import React from 'react'
export function TextBox(props) {
return (
<div>
<input type={props.type}
name={props.name}
value={props.value}
onChange={(event) => props.HandleInput(event)}
/>
</div>
)
}
So what I don't know is how to pass the event handler to the input component, because as it is right now it can't find it.
It should exist in Textbox's props as handleChange as it's passed in the parent component. Does this not work?
<input
type={props.type}
name={props.name}
value={props.value}
onChange={(event) => props.handleChange(event)}
/>

Resources