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

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

Related

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

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)[];

Updating Formik State whilst using Headless UI "Listbox" (which manages its own state, internally)

I'm struggling to get a Headless UI Listbox implementation working successfully with Formik. After reading all the docs and scouring StackOverflow, I'm yet to find an answer.
I can get the Listbox to function perfectly well on its own, and Formik is working with other, less complex (but still custom) components. That said, I can't get them working in unison. Whenever I change the selection in the Select component, I can successfully update the Headless UI state (in as much as it updates to the correct value) and the Formik state, but for some reason the active and selected properties (within Headless UI) aren't working correctly (always false).
I assume what I need to do is to make use of the onChange handler so that it updates both the Headless UI state (to keep the active and selected properties updated) and the Formik value, but this doesn't seem to work as expected.
Does anyone have any suggestions? Please see a minimal representation of the code below:
export function TestForm() {
return (
<Formik
initialValues={{ testing: {} }}
onSubmit={ alert("Submitted"); }
>
<Form>
<Select
name="testing"
items={[
{ id: 1, name: "Test 1" },
{ id: 2, name: "Test 2" },
{ id: 3, name: "Test 3" },
]}
/>
</Form>
)
}
export function Select(props) {
// Get field properties
const [field, meta, helpers] = useField(props);
// Set initial value for `Select`
const [selectedItem, setSelectedItem] = useState(null);
// On change, update `Headless UI` and `Formik` values
const handleChange = (newValue) => {
setSelectedItem(newValue); // Update the Headless UI state
helpers.setValue(newValue); // This seems to "break" the Headless UI state
};
// Return `Select` structure
return (
<Listbox
value={selectedItem}
name={props.name}
onChange={handleChange}
// onBlur={field.onBlur} ...Commented out for now as trying to figure out issue with `onChange`
>
<Listbox.Label>Test Label:</Listbox.Label>
<Listbox.Button>
{selectedItem ? selectedItem.name : "-- Select --"}
</Listbox.Button>
<Listbox.Options>
{props.items.map((item) => {
return (
<Listbox.Option
className={({ active }) => (active ? "active" : "")}
key={item.id}
value={item}
>
{({ selected }) => (
<>
{item.name}
{selected ? <CheckIcon /> : null}
</>
)}
</Listbox.Option>
);
})}
</Listbox.Options>
</Listbox>
);
}
I managed to make it work like this:
import { useField } from "formik";
interface SelectProps {
name: string;
label: string;
options: string[];
}
export const Select: React.FC<SelectProps> = ({
name,
label,
options,
}) => {
const [field] = useField({ name });
return (
<Listbox
value={field.value}
onChange={(value: string) => {
field.onChange({ target: { value, name } });
}}
>
...
</Listbox>
);
};

How to get params and type definition for custom React/React-Native components

I built a custom React Native Input Component which shows a dark border at the bottom when focused but when I am using this component then I am not getting props suggestion provided by VS Code Intellisense.
function TextInputComponent(props) {
const [controlledValue, setControlledValue] = useState('');
const [focused, setFocused] = useState(false);
const {
placeholder,
onChangeText,
autoCapitalize,
ref,
secureTextEntry,
value,
defaultValue,
controlled,
editable,
...rest
} = props;
return (
<TextInput
ref={ref}
value={controlled ? controlledValue : value}
defaultValue={defaultValue}
secureTextEntry={secureTextEntry}
placeholder={placeholder}
placeholderTextColor={globalColors.semiGray}
autoCapitalize={autoCapitalize}
editable={editable}
mode="outlined"
onFocus={() => {
setFocused(true);
}}
onBlur={() => {
setFocused(false);
}}
style={[
styles.textInput,
focused ? {borderColor: globalColors.blue} : {},
]}
onChangeText={text => {
if (controlled) {
console.log(text.toLowerCase());
setControlledValue(text.toLowerCase());
onChangeText(text);
} else {
onChangeText(text);
}
}}
{...rest}
/>
);
}
const styles = StyleSheet.create({
textInput: {
...elementStyles.input,
},
});
Is there any way I can get prop and type definitions without using TypeScript?
/**
*
* #param {{header: string, subheader: string, imageAlt: string, contentList: Array, orderLink: Object, contentLink: Object}} props
*/
I have seen #params way of definition but I just want to extend the default TextInput Definition not to write all the type definitions again for my custom component.
check Prop-Types . you can easily do prop checking with it

Keeping state of variable mapped from props in functional react component after component redraw

Recently I started learning react and I decided to use in my project functional components instead of class-based. I am facing an issue with keeping state on one of my components.
This is generic form component that accepts array of elements in order to draw all of necessary fields in form. On submit it returns "model" with values coming from input fields.
Everything working fine until I added logic for conditionally enabling or disabling "Submit" button when not all required fields are set. This logic is fired either on component mount using useEffect hook or after every input in form input. After re-render of the component (e.g. conditions for enabling button are not met, so button becomes disabled), component function is fired again and my logic for creating new mutable object from passed props started again, so I am finished with empty object.
I did sort of workaround to make a reference of that mutated object outside of scope of component function, but i dont feel comfortable with it. I also dont want to use Redux for that simple sort of state.
Here is the code (I am using Type Script):
//component interfaces:
export enum FieldType {
Normal = "normal",
Password = "password",
Email = "email"
}
export interface FormField {
label: string;
displayLabel: string;
type: FieldType;
required: boolean;
}
export interface FormModel {
model: {
field: FormField;
value: string | null;
}[]
}
export interface IForm {
title: string;
labels: FormField[];
actionTitle: string;
onSubmit: (model: FormModel) => void;
}
let _formState: any = null;
export function Form(props: IForm) {
let mutableFormModel = props.labels.map((field) => { return { field: field, value: null as any } });
//_formState keeps reference outside of react function scope. After coponent redraw state inside this function is lost, but is still maintained outside
if (_formState) {
mutableFormModel = _formState;
} else {
_formState = mutableFormModel;
}
const [formModel, setFormModel] = useState(mutableFormModel);
const [buttonEnabled, setButtonEnabled] = useState(false);
function requiredFieldsCheck(formModel: any): boolean {
let allRequiredSet = true;
formModel.model.forEach((field: { field: { required: any; }; value: string | null; }) => {
if (field.field.required && (field.value === null || field.value === '')) {
allRequiredSet = false;
}
})
return allRequiredSet;
}
function handleChange(field: FormField, value: string) {
let elem = mutableFormModel.find(el => el.field.label === field.label);
if (elem) {
value !== '' ? elem.value = value as any : elem.value = null;
}
let submitEnabled = requiredFieldsCheck({ model: mutableFormModel });
setFormModel(mutableFormModel);
setButtonEnabled(submitEnabled);
}
useEffect(() => {
setButtonEnabled(requiredFieldsCheck({ model: mutableFormModel }));
}, [mutableFormModel]);
function onSubmit(event: { preventDefault: () => void; }) {
event.preventDefault();
props.onSubmit({ model: formModel })
}
return (
<FormStyle>
<div className="form-container">
<h2 className="form-header">{props.title}</h2>
<form className="form-content">
<div className="form-group">
{props.labels.map((field) => {
return (
<div className="form-field" key={field.label}>
<label>{field.displayLabel}</label>
{ field.type === FieldType.Password ?
<input type="password" onChange={(e) => handleChange(field, e.target.value)}></input> :
<input type="text" onChange={(e) => handleChange(field, e.target.value)}></input>
}
</div>
)
})}
</div>
</form>
{buttonEnabled ?
<button className={`form-action btn btn--active`} onClick={onSubmit}> {props.actionTitle} </button> :
<button disabled className={`form-action btn btn--disabled`} onClick={onSubmit}> {props.actionTitle} </button>}
</div>
</FormStyle >
);
}
So there is quite a lot going on with your state here.
Instead of using a state variable to check if your button should be disabled or not, you could just add something render-time, instead of calculating a local state everytime you type something in your form.
So you could try something like:
<button disabled={!requiredFieldsCheck({ model: formModel })}>Click me</button>
or if you want to make it a bit cleaner:
const buttonDisabled = !requiredFieldsCheck({model: formModel});
...
return <button disabled={buttonDisabled}>Click me</button>
If you want some kind of "caching" without bathering with useEffect and state, you can also try useMemo, which will only change your calculated value whenever your listeners (in your case the formModel) have changes.
const buttonDisabled = useMemo(() => {
return !requiredFieldsCheck({model: formModel});
}, [formModel]);
In order to keep value in that particular case, I've just used useRef hook. It can be used for any data, not only DOM related. But thanks for all inputs, I've learned a lot.

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

Resources