How to connect react-hook-form ref with a custom input component - reactjs

I'm interested in using react-hook-form for data validation. I have a custom TextField component as follows.
src/components/Fields.js
function Label({ id, children }) {
return (
<label
htmlFor={id}
className="block mb-3 text-sm font-medium text-gray-700"
>
{children}
</label>
);
}
export function TextField({
id,
label,
inputRef,
type = "text",
className = "",
...props
}) {
return (
<div className={className}>
{label && <Label id={id}>{label}</Label>}
<input id={id} ref={inputRef} type={type} {...props} />
</div>
);
}
I've tried using react-hook-form like this..
src/App.js
import { TextField } from "./components/Fields";
import { useForm } from "react-hook-form";
import { useEffect } from "react";
export default function App() {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const mytest = register("mytest", { required: "mytest is a required field" });
const onSubmit = (data) => console.log(data);
useEffect(() => console.log(errors), [errors])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
id="mytest"
name={mytest.name}
inputRef={mytest.ref}
label="This is a test"
placeholder="Placeholder"
/>
<input type="submit" />
</form>
);
}
but it's not working properly.
I've also tried using forwardRef to no avail.

Finally got this working after applying forwardRef properly. Still curious if there's a better way, so I'll leave this question open.
src/components/Fields.js
import { forwardRef } from "react";
function Label({ id, children }) {
return (
<label
htmlFor={id}
className="block mb-3 text-sm font-medium text-gray-700"
>
{children}
</label>
);
}
export const TextField = forwardRef(function TextField({
id,
label,
type = "text",
className = "",
...props
}, ref) {
return (
<div className={className}>
{label && <Label id={id}>{label}</Label>}
<input id={id} ref={ref} type={type} {...props} />
</div>
);
});
src/App.js
import { TextField } from "./components/Fields";
import { useForm } from "react-hook-form";
import { useEffect } from "react";
export default function App() {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const mytest = register("mytest", { required: "mytest is a required field" });
const onSubmit = (data) => console.log("data", data);
useEffect(() => console.log(errors), [errors])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
id="mytest"
label="This is a test"
placeholder="Placeholder"
{...mytest}
/>
{errors.mytest && <>{errors.mytest.message}</>}
<input type="submit" />
</form>
);
}

Related

Set props Data to context in formik

I have two components inside contact page. One is contact form and another is FormData where I'm showing what the user is typing in forms field. I also have a context where I want to store the form data. But in formik there are built-in props, from there I can access the values but from inside the form. But I want to pass the values outside the form to the context so that I can access this data from the FormData component.
Contact Form
import React, { useContext, useEffect } from "react";
import { useFormik } from "formik";
import { Formik } from "formik";
import { ContentContext } from "../Context";
import { FormData } from "./index";
const ContactForm = () => {
const [content, setContent] = useContext(ContentContext);
// useEffect(() => {
// setContent({
// ...content,
// contactFormData: props,
// });
// }, [props]);
// console.log(content);
return (
<Formik
initialValues={{ email: "" }}
onSubmit={async (values) => {
await new Promise((resolve) => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
// setContent({
// ...content,
// contactFormData: props,
// });
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
{/* <FormData props={props} />; */}
</form>
);
}}
</Formik>
);
};
export default ContactForm;
context
import React, { useState, createContext } from "react";
export const ContentContext = createContext();
export const ContentProvider = ({ children }) => {
const [content, setContent] = useState({
contactFormData: {
email: "",
},
});
return (
<ContentContext.Provider value={[content, setContent]}>
{children}
</ContentContext.Provider>
);
};
setting the context inside the form causes infinite loop. How do I save props to context?

Required rule for group of checkboxed

Shared answer https://stackoverflow.com/a/71804470/20847211
**How can i use required rule for group of checkboxes in this code **
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: { months: ['January'] },
})
const onSubmit = (data: any) => console.log(data)
console.log(errors)
return (
<div className='mx-auto justify-center p-32 flex'>
<form onSubmit={handleSubmit(onSubmit)}>
<div className='p-2'>
<label htmlFor=''>January</label>
<input
type='checkbox'
value='January'
placeholder='January'
{...register('months')}
className='mx-3'
/>
</div>
<div className='p-2'>
<label htmlFor=''>February</label>
<input
type='checkbox'
value='February'
placeholder='February'
{...register('months')}
className='mx-3'
/>
</div>
I tried to place required to fields, but it'nt working

onChange access funtion not working in react js

I have this toggle react component. I am trying to access handleChangeTogle when i click on the Toggle but call is not reaching handleChangeTogle.
What am i doing wrong here?
const handleChangeTogle = () => {
setdomestic_voilence(!domestic_voilence);
};
<Toggle
checked={domestic_voilence}
text="Is Active"
onChange={() => handleChangeTogle}
offstyle="btn-danger"
onstyle="btn-success"
/>
import React from "react";
function Toggle(props) {
console.log(props);
const {
text,
size = "default",
defaultChecked,
disabled,
onChange,
offstyle = "btn-danger",
onstyle = "btn-success",
} = props;
let displayStyle = defaultChecked ? onstyle : offstyle;
return (
<>
<label>
<span className={` switch-wrapper`}>
<input
type="checkbox"
// checked={defaultChecked}
// onChange={(e) => onChange(e)}
/>
<span className={`${displayStyle} switch`}>
<span className="switch-handle" />
</span>
</span>
{/* <span className="switch-label">gyyghiyg</span> */}
</label>
</>
);
}
export default Toggle;
I tested your problem and solved it this way. (I used typescript with React. You can remove Interface and types).
Toggle.tsx
import React from "react";
import { ToggleInterface } from './interfaces';
const Toggle = ({ text, handleChange, offstyle, onstyle }: ToggleInterface) => {
return (
<>
<label>
<span className={`switch-wrapper`}>
<input
type="checkbox"
onChange={(e) => handleChange(e)} />
<span className="switch">
<span className="switch-handle" />
</span>
</span>
</label>
</>
);
}
export default Toggle;
App.tsx
const handleChangeToggle = () => {
console.log("handleChangeToggle is worked !!");
};
return (
<div className="App">
<Toggle
text="Is Active"
handleChange={() => handleChangeToggle() }
offstyle= "btn-danger"
onstyle= "btn-success"
/>
);
}
export default App;
interface.ts
export interface ToggleInterface {
text: string,
handleChange: Function,
offstyle: string,
onstyle: string
}

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>
);
};

TypeError: register is not a function using React Hook Form in React

The Error Message:
If i dont use the Inputs inside div then it works perfectly but when i use Input inside div it shows me this error.
I wanted to keep the hook related stuff separated so it look clean.
why does it only works when its not inside a div?
Login.tsx
import { useHistory } from "react-router-dom";
import { useForm } from "react-hook-form";
import useAuth from "./../hooks/useAuth";
import { Form, Input } from "../components/FormGroup";
import MacNav from "../components/MacNav";
import { loginActionUrl } from "./../services/ApiLinks";
import {
fetchPostResopnse,
successPopUp,
errorPopUp,
} from "./../services/FormHelper";
type Tinputs = {
username: string;
password: string;
};
function Login() {
const auth = useAuth();
const history = useHistory();
const methods = useForm<Tinputs>();
const onSubmit = async (data: Tinputs) => {
const result = await fetchPostResopnse(loginActionUrl, data);
if (result.isAuth) {
successPopUp("Credentials Matched", () => {
auth.signIn(result);
history.push("/admin/dashboard");
});
} else {
errorPopUp("Credentials Does Not Matched");
}
};
return (
<div>
<MacNav />
<div className="section-secondary">
<div className="container">
<div className="contact-form-wrapper">
<div className="title-lg text-center">Enter Your Credentials</div>
<Form formMethods={methods} handler={onSubmit} submitBtn="Submit">
{/*If i dont use Input inside div it works*/}
<div>
<Input name="username" rule={{ required: true }} />
</div>
<Input name="password" rule={{ required: true }} />
</Form>
</div>
</div>
</div>
</div>
);
}
export default Login;
I have wrote the form components here.
FormGroup.tsx
import React from "react";
const Form = ({ children, formMethods, handler, submitBtn }: any) => {
return (
<form onSubmit={formMethods.handleSubmit(handler)}>
{React.Children.map(children, (child) => {
return child.props.name ? (
<div>
{React.createElement(child.type, {
...{
...child.props,
register: formMethods.register,
key: child.props.name,
},
})}
{child.props?.rule && formMethods.errors[child.props.name] && (
<div className="text-danger">
*
{formMethods.errors[child.props.name].message
? formMethods.errors[child.props.name].message
: `${child.props.name} is required`}
</div>
)}
</div>
) : (
child
);
})}
{submitBtn && <button type="submit">{submitBtn}</button>}
</form>
);
};
const Input = ({ register, name, label, rule, ...rest }: any) => {
label = label ? label : name?.charAt(0).toUpperCase() + name?.slice(1);
return (
<div>
<label htmlFor={name}>{label}</label>
<input name={name} ref={register(rule)} {...rest} />
</div>
);
};
const Textarea = ({ register, name, label, rule, ...rest }: any) => {
label = label ? label : name?.charAt(0).toUpperCase() + name?.slice(1);
return (
<div>
<label htmlFor={name}>{label}</label>
<textarea name={name} ref={register(rule)} {...rest}></textarea>
</div>
);
};
const SubmitButton = ({ name, ...rest }: any) => {
return (
<button type="submit" {...rest}>
{name}
</button>
);
};
export { Form, Input, Textarea, SubmitButton };
[1]: https://i.stack.imgur.com/PvEUA.png
Hello according to your code, what happened it's expected
the div doesn't have name so according to this code
{React.Children.map(children, (child) => {
return child.props.name ? (
<div>
{React.createElement(child.type, {
...{
...child.props,
register: formMethods.register,
key: child.props.name,
},
})}
{child.props?.rule && formMethods.errors[child.props.name] && (
<div className="text-danger">
*
{formMethods.errors[child.props.name].message
? formMethods.errors[child.props.name].message
: `${child.props.name} is required`}
</div>
)}
</div>
) : (
child
);
})}
And the below child
<div>
<Input name="username" rule={{ required: true }} />
/div>
The Input component will be rendrered without register prop, so when it will try to call it here, however it's value is undefined, what will cause an error
ref={register(rule)}
I suggest to create a new component
const InputWithDiv = (props) => (
<div>
<Input rule={{ required: true }} {..props} />
/div>
);
and use it like below
<Form formMethods={methods} handler={onSubmit} submitBtn="Submit">
<InputWithDiv name="username" />
<Input name="password" rule={{ required: true }} />
</Form>

Resources