I am trying to render customInput in React currency input field, What I have so far is:
import { TextField, TextFieldProps } from '#mui/material';
import React from 'react';
import CurrencyInput from 'react-currency-input-field';
import { Controller, RegisterOptions, useFormContext } from 'react-hook-form';
type IProps = {
name: string;
rules?: RegisterOptions;
defaultValue?: string;
};
type Props = IProps & TextFieldProps;
export default function RHFCurrencyField({ name, rules, defaultValue, ...other }: Props) {
const { control } = useFormContext();
return (
<Controller
name={name}
rules={rules}
control={control}
render={({ field, fieldState: { error } }) => (
<CurrencyInput
name={name}
groupSeparator=" "
defaultValue={defaultValue}
decimalsLimit={2}
onValueChange={(value, name) => {
field.onChange(value);
}}
customInput={() => <CurrencyTextField {...other} {...field} />}
/>
)}
/>
);
}
export const CurrencyTextField = React.forwardRef((props: Props, ref: any) => {
return <TextField {...props} ref={ref} />;
});
And I am getting a warning, and input i
react-dom.development.js:67 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of CurrencyInput.
What am I missing ?
Related
Can't understand how to validate and render error massages in my FormInput component (below of input tag).
Advice please.
No understanding of error output.`
SmartForm
import React from "react";
import { useForm, get } from "react-hook-form";
export default function SmartForm({
defaultValues,
register,
children,
...rest
}) {
const props = { ...rest };
const methods = useForm(defaultValues);
const {
handleSubmit,
formState: { errors },
} = methods;
return (
<>
<form onSubmit={handleSubmit}>
{React.Children.map(children, (child) => {
return child.props.name
? React.createElement(child.type, {
...{
...child.props,
register: methods.register,
key: child.props.name,
},
})
: child;
})}
{Object.values(errors)}
<input type="submit"></input>
</form>
</>
);
}
`
FormInput
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
LoginForm
import React from "react";
import SmartForm from "../SmartModalForm/smartModalForm";
import FormInput from "./FormInput/formInput";
export default function LoginForm(props) {
const onSubmit = (e) => {
e.preventDefault();
console.log(e);
};
return (
<SmartForm
handleClose={handleClose}
handleSubmit={onSubmit}
>
<FormInput
name="email"
rules={{ required: "This is required." }}
/>
<FormInput
name="password"
rules={{ required: "This is required." }}
/>
</SmartForm>
);
}
import React from "react";
export default function FormInput({ register, rules, name, error, ...rest }) {
return (
<>
<input
{...register(
name,
rules
)}
{...rest}
className={styles.input}
/>
{error?.name && <div className={styles.error}>{error.name.message}</div>}
</>
);
}
In SmartFormComponent you are just passing register from useFormReturn and not all methods. So, in FormInput, error is undefined.
Update React.createElement in SmartFormComponent as below
React.createElement(child.type, {
...{
...child.props,
methods, // not just register, but pass entire `useFormReturn` values
key: child.props.name,
},
})
I have a CustomTextBox component that is wrapped in react-hook-form Controller component and all works fine including validation and displaying the error message with ErrorMessage component.
The only thing left to-do is to setFocus on the fields when there is errors in the form. This is my first TypeScript project and i'm struggling to find solutions which are similar to mine.
I tried useRef but this only give compile time error => "Property 'ref' does not exist on type 'IntrinsicAttributes".
Below is my Custom component. Please guys all help will be appreciated. Thanks in advance
import React, { useRef } from "react";
import TextField from '#material-ui/core/TextField';
import { Control, Controller } from "react-hook-form";
import { Keys } from '../Profile/interfaces';
interface Props {
id: string;
label: string,
variant: "filled" | "standard" | "outlined",
disabled?: boolean,
control: Control<any,any>
required?: boolean,
name: Keys,
requiredMsg?: string
}
const CustomTextBox: React.FC<Props> = ({id, label, variant,disabled=false, control,
required=false, name, requiredMsg}) => {
const inputRef = useRef<React.RefObject<HTMLButtonElement>>();
return (
<Controller
ref={inputRef}
name={name}
control={control}
rules={{required: required ? requiredMsg : null}}
render={({ field }) =>
<TextField inputRef={field.ref} InputLabelProps={{ shrink: true }} id={id} label={label} variant={variant}
disabled={disabled} {...field}
style={{marginTop: '10px', marginBottom: '10px', minWidth: '250px'}} /> }
/>
);
}
export default CustomTextBox;
So thanks to #Victor Luft i was able to get it right with the below code. Also this is not in the component itself but rather on the page/component that uses Custom Components. This will focus on any element that has an error in your react-hook-form form tag. I hope that makes sense.
Thanks again Victor
useEffect(() => {
const firstErrorKey = Object.keys(errors).find((key) => errors[key]);
if (firstErrorKey) {
(document.querySelector(
`input[name="${firstErrorKey}"]`
) as HTMLInputElement | null)?.focus();
}
}, [Object.keys(errors)]);
You are using field.ref as inputRef for the TextField component. It is very likely that this will be assigned to the native <input /> element. And that is the one, you want to be able to call focus() on.
import React, { useRef } from "react";
import TextField from '#material-ui/core/TextField';
import { Control, Controller } from "react-hook-form";
import { Keys } from '../Profile/interfaces';
interface Props {
id: string;
label: string,
variant: "filled" | "standard" | "outlined",
disabled?: boolean,
control: Control<any,any>
required?: boolean,
name: Keys,
requiredMsg?: string
}
const CustomTextBox: React.FC<Props> = ({id, label, variant,disabled=false, control,
required=false, name, requiredMsg}) => {
const inputRef = useRef<React.RefObject<HTMLButtonElement>>();
return (
<Controller
ref={inputRef}
name={name}
control={control}
rules={{required: required ? requiredMsg : null}}
render={({ field, fieldState }) => {
// don't know who to compute this state as I don't know this lib
const hasError = fieldState === 'error';
if (field.ref.current && fieldState === 'error') field.ref.current.focus();
return (
<TextField inputRef={field.ref} InputLabelProps={{ shrink: true }} id={id} label={label} variant={variant}
disabled={disabled} {...field}
style={{marginTop: '10px', marginBottom: '10px', minWidth: '250px'}} />);
}}
/>
);
}
export default CustomTextBox;
Getting the error in browser Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
My code:
import { yupResolver } from '#hookform/resolvers/yup'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { contactSchema } from 'schemas/schemas'
import { InputFloatLabel } from './components/Inputs/InputFloatLabel'
type TypeFormInput = {
name: string
email: string
textarea: string
}
export const Register = () => {
const [isLoading, setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<TypeFormInput>({ resolver: yupResolver(contactSchema) })
const onSubmit: SubmitHandler<TypeFormInput> = async ({ name, email }) => {
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ email', email)
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ name', name)
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<InputFloatLabel
type="text"
placeholder="Name"
{...register('name')}
/>
<button type="submit">{isLoading ? 'Loading' : 'Send Mail'}</button>
</div>
</form>
</div>
)
}
And the Input comp:
import { useState } from 'react'
type typeInput = {
placeholder: string
type?: string
}
export const InputFloatLabel: React.FC<typeInput> = ({ type, placeholder, ...props }) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
}
I don't have this issue with ChakraUI that I've built but now just doing plain input as a separate component getting that issue.
I have tried some suggestions from here, but still can't fix it: https://github.com/react-hook-form/react-hook-form/issues/85
So the issue is that I think that the {...register("name"}} line actually includes a ref property. You could console.log that out to verify; this is what I found to be true when using {...field} with the ControlledComponent. A very quick fix to get rid of the console error is to just, after the line with the spread, to add a ref={null} to override this ref that is being passed in from the library.
You forgot to forward the ref in your InputFloatLabel. See https://reactjs.org/docs/forwarding-refs.html
In your case it would look like this:
export const InputFloatLabel: React.FC<typeInput> =
// Use React.forwardRef
React.forwardRef(({type, placeholder, ...props}, ref) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
ref={ref /* Pass ref */}
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
})
In https://react-hook-form.com/faqs, scroll to "How to share ref usage?" may help?
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const firstNameRef = useRef(null);
const onSubmit = data => console.log(data);
const { ref, ...rest } = register('firstName');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...rest} name="firstName" ref={(e) => {
ref(e)
firstNameRef.current = e // you can still assign to ref
}} />
<button>Submit</button>
</form>
);
}
the field element and register function pass a ref to the element. If you define a custom React Component and try to use it within a controller or with register, funky things can happen. I found using React.forwardRef() solves the problem when using Custom Components.
CustomSwitchComponent.tsx
import React from 'react';
import {FormControlLabel, Switch, SxProps, Theme} from "#mui/material";
const TradingStrategyEditFormInstructionsInputSwitch:
// Note this type allows us to do React.forwardRef(Component)
React.ForwardRefRenderFunction<HTMLButtonElement, {
label: string,
checked: boolean,
onBlur: React.FocusEventHandler<HTMLButtonElement>
}> = ({label, ...rest}) => {
// Spreading ...rest here will pass the ref :)
return <FormControlLabel control={<Switch {...rest} />}
labelPlacement={"top"}
label={label}
/>;
};
// *Huzzah!*
export default React.forwardRef(TradingStrategyEditFormInstructionsInputSwitch);
CustomSwitchController.tsx
<Controller
control={formCtx.control}
name={fieldPath}
key={type + side + key}
render={({field}) => {
return <TradingStrategyEditFormInstructionsInputField
{...field}
label={key}
checked={field.value}
onBlur={() => {
field.onBlur()
handleOnBlur(key)
}}
/>
}}/>
Idk if we're allowed to link YT Videos, but techsith has a great video on using forwardRef with useRef that should clear things up.
https://www.youtube.com/watch?v=ScT4ElKd6eo
Your input component does not export ref as props since it is a functional component.
React.useEffect(() => {
register('name', { required: true });
}, []);
<InputFloatLabel
type="text"
placeholder="Name"
name="name"
// Remove the register from here
/>
I don't know how I must type the props parameter on formik component, someone can help me please?
import React from 'react';
import { useField, FormikProps } from 'formik';
interface InputFields {
id: string;
name: string;
}
interface InputForm {
label: string;
props: FormikProps<InputFields>;
}
const Input = ({ label, ...props }: InputForm): JSX.Element => {
const [field, meta] = useField(props);
return (
<>
<label htmlFor={props.id || props.name}>{label}</label>
<input className="text-input" {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
export default Input;
In the code above I get an error on in the useField, props.id and props.name
How about this
import React from "react";
import { useField, FieldHookConfig } from "formik";
interface OtherProps {
label: string;
}
const Input = ({ label, ...props }: OtherProps & FieldHookConfig<string>) => {
const [field, meta] = useField(props);
return (
<>
<label htmlFor={props.id || props.name}>{label}</label>
<input className="text-input" {...field} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
export default Input;
useField accepts either a string of a field name or an object as an argument. It is typed like this
export declare function useField<Val = any>(propsOrFieldName: string | FieldHookConfig<Val>): [FieldInputProps<Val>, FieldMetaProps<Val>, FieldHelperProps<Val>];
Here we used FieldHookConfig<string>.
You only really need to type the props of the Input component. TypeScript can figure out the rest on its own.
I'm defining a useForm.
const { handleSubmit, control, errors } = useForm<{email: string}>();
Now I'm creating a seperate component that will the input and I'm going to pass the useForm props i created above.
This how that Components look like.
type Props<T> = {
name: FieldName<T>;
control: Control<T>;
errors: FieldErrors<T>;
};
const ControlTextInput = <T extends {}>({
name,
control,
errors,
}: Props<T>) => {
return (
<Controller
name={name}
control={control}
rules={{
required:'this is required',
}}
render={({ onChange }) => (
<>
<TextInput
onChangeText={(text) => {
onChange(text);
}}
/>
{/* Show my error here */}
{errors.email && (
<Text style={{ color: "red" }}>
{errors.email?.message}
</Text>
)}
</>
)}
/>
);
};
I want to use the component like this.
<ControlTextInput<AnyObject>
name="email"
errors={errors}
control={control}
/>
I get this error when i hover over the errors.email
React Hook Form exposes type UseControllerProps which accepts generic type T which infers your input value types or in other words the FieldValues type. Initially you define FieldValues type by passing type about your fields to useForm hook (see MyInputTypes below).
interface MyInputTypes {
email: string;
password: string;
}
const { register, handleSubmit, watch, formState: { errors } } = useForm<MyInputTypes>();
This means you can create interface which extends UseControllerProps and has your generic T interface Props<T> extends UseControllerProps<T> {}. Type UseControllerProps already includes type definitions for name and control and therefore you will not need to define them separately in your interface (unless you want to or there is a particular requirement / reason to do so). Regarding errors appropriate solution Imo (as you require only error about single field) would be to pass that particular error directly which has type FieldError | undefined. The result looks like below code.
interface Props<T> extends UseControllerProps<T> {
error: FieldError | undefined
}
Then you can simply use your ControlTextInput as below.
<ControlTextInput name="email" error={errors.email} />
In the Component (which uses ControlTextInput) your generic T must extend FieldValues as eventually, it is this type which infers types about the fields.
As an example ControlTextInput
import React from 'react';
import { Controller, FieldError, FieldValues, UseControllerProps } from 'react-hook-form';
interface Props<T> extends UseControllerProps<T> {
error: FieldError | undefined;
}
const ControlTextInput = <T extends FieldValues>({ name, control, error }: Props<T>) => {
return (
<Controller
name={name}
control={control}
rules={{
required: 'This is required',
}}
render={({ field: { onChange } }) => (
<>
<input
onChange={(text) => {
onChange(text);
}}
/>
{error && <span style={{ color: 'red' }}>{error?.message}</span>}
</>
)}
/>
);
};
export default ControlTextInput;
As an example Component which uses ControlTextInput
import React, { FunctionComponent } from 'react';
import { useForm } from 'react-hook-form';
import ControlTextInput from './ControlTextInput';
interface InputTypes {
email: string;
password: string;
}
const Foo: FunctionComponent = () => {
const {
formState: { errors },
} = useForm<InputTypes>();
return <ControlTextInput name='email' error={errors.email} />;
};
export default Foo;
Here are screenshots with ready code which mimics more or less your approach and solution (same as code above as new to StackOverflow).
ControlTextInput
Component which uses ControlTextInput
The way I found a solution is to use error type any
errors?: any;
Use the lodash get function
const errName = get(errors, name);
Then i can get the error has follow.
{errName && <Text style={{ color: "red" }}>{errName.message}</Text>}