How to get floatValue automatically from react-number-format inside a antd Form.Item? - reactjs

I'm looking for a way to antd Form get automatically a floatValue from react-number-format.
`<Form.Item
label="My label"
name="fator"
>
<NumericFormat
thousandSeparator="."
decimalSeparator=","
decimalScale={2}
prefix="R$"
fixedDecimalScale={true}
onValueChange={(values, sourceInfo) => {
setState({
...state,
fator: values.floatValue!,
});
}}
/>
</Form.Item>`
Some people used onValueChange to get floatValue, but I do not know if it is the only way or the best way to get that value. On that way, we have to check each field to assign the correct value.
In my opinion, it is not the best way. It is so manually work.
const submit = async (values: stateModel) => {
values.fator = state.fator;
...
...
await doRegister(values);
..
}
How the best way to solve the problem?
I tried using some replacement on values returned from form submit, but it is not the best way to solve the problem.

You can solve this issue by creating a custom Number Field, which takes value and onChange as props.
const CustomNumberField = ({ value, onChange }) => {
return (
<NumericFormat
value={value}
thousandSeparator='.'
decimalSeparator=','
decimalScale={2}
prefix='R$'
fixedDecimalScale={true}
onValueChange={(values, sourceInfo) => {
onChange(values.floatValue);
}}
/>
);
};
Now the main Form will look like this:
const App = () => {
return (
<Form
onFinish={(values) => {
console.log(values); // { factor: 12 }
}}
>
<Form.Item label='My label' name='fator'>
<CustomNumberField />
</Form.Item>
{/* OR */}
{/* <Form.Item name='field'>
{(control, meta, form) => {
return <CustomNumberField {...control} />;
}}
</Form.Item> */}
<Button htmlType='submit'>Submit</Button>
</Form>
);
};
Q: Where that value & onChange prop come from?
When you pass name prop to Form.Item, the field inside the Form.Item is now controlled by Form. You can either pass a ReactElement or a function. For ReactElement, it pass two props value & onChange. For callback function, it looks like this:
children?: React.ReactElement | ((control: ChildProps, meta: Meta, form: FormInstance<Values>) => React.ReactNode);
interface ChildProps {
[name: string]: any;
}
interface Meta {
touched: boolean;
validating: boolean;
errors: string[];
warnings: string[];
name: InternalNamePath;
}
type InternalNamePath = (string | number)[];

Related

React hook form pass an array of values to child - some Regster and some not

I want to pass an array of strings to my Child Component which creates an input field managed by React Hook Form.
Sometimes, I want the React Hook Form to NOT register some fields in the array passed as the input fields are optional to the user. For example, address_line2 in the inputForm array is not always required to be completed on the form.
I use this Child Component in a number of places, so if I change it, is it possible to make the change or component optional or use an if statement? So, that i can continue to use the child component as is.
Here is the parent component:
const StopForm: React.FC<IProps> = ({ id, index, stop }) => {
const Router = useRouter();
const {
register,
handleSubmit,
unregister,
formState: { errors },
} = useForm<Stop>({
defaultValues: sampleStopData,
});
const inputFields = [
"address_line1",
"address_line2",
];
return (
<>
{inputFields.map((field, index) => (
<InputField
key={index}
val={field}
register={register}
defaultValue={
typeof stop === "object" &&
typeof stop[field as keyof Stop] !== "undefined"
? // ? stop[field as keyof Stop].toString()
String(stop[field as keyof Stop])
: ""
}
errors={errors}
classes="px-3 py-1.5 rounded placeholder:text-gray-800 lg:w-[25rem] lg:mx-auto bg-transparent border outline-none"
/>
))}
</>);
My Child Component that manages the react hook form part is this:
interface IProps {
val: string;
type?: string;
defaultValue: string;
register: UseFormRegister<any>;
errors: any;
classes: string;
}
const InputField: React.FC<IProps> = ({
defaultValue,
register,
errors,
val,
classes,
type = "text",
}) => {
return (
<>
<input
id={val}
type={type}
placeholder={val}
className={`${classes} ${
errors?.val ? "border-red-900" : "border-gray-400"
}`}
defaultValue={defaultValue}
{...register(val, {
required: { value: true, message: `${val} is required` },
})}
/>
{errors[val] && (
<small className="text-red-700">{errors[val].message}</small>
)}
</>
);
};
export default InputField;
Can the input fields also pass a value or somehow let the child component know to 'skip' that value and not register it?
Any assistance on how to do this - I have been going round and round on this one, wouuld be graciously received!
Thank you!
Karen

What is the Antd type for form onFinish?

Right now I am using this everywhere in my code:
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const handleCreate = (input: any): void => {
saveToBackend({
title: input.title,
other: input.other,
// ...
})
};
<Form onFinish={handleCreate}>
// ...
</Form>
What is the type for the input I should be using to make things safer? Also, where are the antd types so I can look up what their implementation is like, so I know how to use them in other situations as well?
If the type is actually any, what should I be doing to make this more typesafe?
You can define create an interface for all the fields wrapped inside the form & use it as a type in where you want to use.
you can also add type in useForm Hook.
import { Checkbox, Form, Input } from 'antd';
import './style.css';
const { Item } = Form;
interface FormRule {
name: string;
isVerified: boolean;
}
export default function App() {
const [form] = Form.useForm<FormRule>();
const onFinish = (data: FormRule) => {};
return (
<Form
form={form}
requiredMark={false}
layout='vertical'
// onFinish={(data) => {}} // => Write `data.`. It will show all the keys or Hover on `data` (Shows The Type of data [FormRule] )
onFinish={onFinish}
>
<Item name='name' label='Name'>
<Input />
</Item>
<Item name='isVerified' label='Verified' valuePropName='checked'>
<Checkbox />
</Item>
</Form>
);
}
If you add type in useForm, you can take advantage of editor intellisense when you set fields values:
form.setFieldsValue({ ... })}

How to pass string only rather than the entire event in the onchange?

Hi I am now working on a project. There were 2 component inside.
InputDialog & AuthenComponent.
I am now trying to keep track of the string inside the HTMLTextAreaElement.
My way of doing thing is working fine but one of the senior want a different way.
I will now show you the working version.
InputDialog Component
interface InputDialogContentProps {
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => string;
}
export default function InputDialog(props: InputDialogContentProps) {
return (
<div className="inputDialogContent">
<DialogContent className="dialogContentPlacement">
<TextField
onChange={props.onChange}
fullWidth
/>
</DialogContent>
</div>
);
}
as you can see I am getting the onchange event from the props.
This is the AuthenComponent ( Function component )
<Dialog
open
onClose={hideDialogBox}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
className="TextPlacement"
>
<div className="AuthenticationDialog">
<InputDialogContent
inputLabel="Email Address"
inputType="email"
onChange={onInputChange}/>
</div>
</Dialog>
function onInputChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
setEnableLoginButton(isValidEmail(e.target.value));
return e.target.value;
}
const isValidEmail = (email: string): boolean => {
const validateFormat = /^\w+([\.-]?\w+)*#\w+$/;
return email.match(validateFormat);
};
These component are working fine. But senior want me to pass the string only rather than passing the entire event. I have been searching google for too long and can't find a soluton.
He want something like this in inputDialog
onChange: (text: string) => void
Rather than
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => string;
Extract the value from the element inside the child component's handler, and call the prop function with it:
interface InputDialogContentProps {
checkValidEmail: (text: string) => void
}
onChange={(e) => { props.checkValidEmail(e.currentTarget.value); }}
And in the parent component:
function checkValidEmail(text: string) {
setEnableLoginButton(isValidEmail(text));
}
You can try
<InputDialogContent
inputLabel="Email Address"
inputType="email"
onChange={(e) => onInputChange(e.target.value)}/>
then your onInputChange function
function onInputChange(email: string) {
setEnableLoginButton(isValidEmail(email));
return email;
}

getting setstate to cause a rerender every time in a useEffect block

I've created this codesandbox example and here is the code:
import React, { ReactNode, useState } from "react";
import { Formik, FormikConfig, FormikProps, Form, FormikErrors } from "formik";
import { useEffect } from "react";
import { scrollToValidationError } from "./scrollToValidationError";
// const isEmpty = (a: unknown): boolean =>
// typeof a === "object" && Object.keys(a).length > 0;
export type FormContainerProps<V> = {
render({
values,
errors,
invalid,
submitCount,
isSubmitting
}: {
values: V;
invalid: boolean;
errors: FormikErrors<V>;
submitCount: number;
isSubmitting: boolean;
}): ReactNode;
additionalContent?: ReactNode;
nextButtonText?: string;
} & Pick<FormikConfig<V>, "initialValues" | "validate"> &
Partial<Pick<FormikConfig<V>, "onSubmit">>;
export const FormContainer = function FormContainer<V>({
initialValues,
additionalContent,
validate,
render,
...rest
}: FormContainerProps<V>) {
const [hasValidationError, setHasValidationError] = useState(false);
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
return (
<>
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={async (values, { validateForm }) => {}}
>
{({
isSubmitting,
submitCount,
isValid,
errors,
values
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
setHasValidationError(true);
}
return (
<>
<div data-selector="validation-summary">Validation Summary</div>
<Form>
<div>
<div>
{render({
values,
errors,
isSubmitting,
invalid,
submitCount
})}
</div>
<div>
<button type="submit">SUBMIT</button>
</div>
</div>
</Form>
</>
);
}}
</Formik>
</>
);
};
Basically I am calling setHasValidationError(true) which breaks the dependency watcher on useEffect
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
setTimeout(() => setHasValidationError(false));
}, [hasValidationError]);
But if this is a form with multiple errors then I want to trigger the useEffect every time but I don't know when to reset it to false or if there is a better way.
In order to scroll to the first error field upon clicking on submit then you can do the following:
Write a custom component (eg: FocuseabelField) that renders formik field which also handles automatic scroll to element and focus on error input
Use Formik's innerRef
Just use the formik's isSubmitting and errors to handle logic for scrolling and focussing
FocuseabelField custom component
const FocuseabelField: any = props => {
const elementRef = useRef<HTMLDivElement>();
if (
props.isSubmitting &&
elementRef.current !== undefined &&
props.errors.hasOwnProperty(props.name)
) {
elementRef.current.scrollIntoView();
elementRef.current.focus();
}
return <Field {...props} innerRef={elementRef} />;
};
Usage
<FocuseabelField
errors={errors}
isSubmitting={isSubmitting}
name="name"
placeholder="enter name"
className={errors && errors.name ? "input error" : "input"}
/>
I have taken your code and have commented the stuff like scrollToValidationerror.ts, dom.ts, wait.ts, useState(hasValidationError), useEffect etc.
Simplified working copy of the code is here. I have used 2 fields to demonstrate multiple errors & auto scroll & focus to the error field:
https://codesandbox.io/s/usemachine-typescript-problems-tns0c?file=/src/components/Home/index.tsx
When the forms gets bigger it becomes complicated to manage so its good to consider outsourcing the form validation part and use libraries such as yup and maintain a validation schema and pass it on to formik.
Have a look at the formik docs for examples.
What about creating an Object with keys for each form field? That way you can maintain a specific form validation error for each input and use that Object in the useEffect second parameter, it will make sure it's triggered for each form error update
To answer your original question,
useEffect does reference check for its dependencies, so you could use Object instead of value. So something like this.
const [hasValidationError, setHasValidationError] = useState({value: false});
useEffect(() => {
if (!hasValidationError.value) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
setHasValidationError({value: true});
But in regards to Formik usage, I highly recommend you follow what #gdh pointed out.
I don't understand the need of effects here.
Why don't you call the method directly instead of using hooks?
You can avoid 2 re-renders by doing that, and the component can also be stateless!
...
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
scrollToValidationError();
}
...

handle onChange using react custom component with formik

I use custom react component with formik which is handled by sending the value where I use it and setting its own state from its parent, so onChange I am always set its react state besides setFieldvalue to set it in formik as I dont use handleChange from formik props.
<Field
render={(fields: { field: object; form: formProps }) => {
return (
<TextField
name="title"
error={errors.title && touched.title}
value={title}
onKeyUp={() => null}
onBlur={handleBlur}
onChange={(e: { target: { value: string } }) =>
this.props.onChange('title', e, fields.form)
}
placeholder="e.g Introduction to UX Design"
/>
);
}}
/>
onChange = (
stateField: string,
e: { target: { value: string } },
form: { setFieldValue: (field: string, value: string) => void }
) => {
// the field is controlled twice here
form.setFieldValue(stateField, e.target.value);
this.setState({ [stateField]: e.target.value });
};
it is working correct but it is a hassle for me to handle the two cases in each field and I am not feeling it is the best way to do so, any help ?
Why do you need to control the value twice? Can you not lift it out of the form state when required, rather than storing in the component state? If you explain the use case more, I think someone will suggest a better way of tackling the problem.

Resources