handle onChange using react custom component with formik - reactjs

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.

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

React using Formik does not clear the data value in Material UI form

I'm using formik.resetForm() to remove values from text fields in a form after submitting the data.
...
const handleSubmitProduct = async (values: Object, resetForm: any) => {
... code to handle my form data ...
resetForm()
if (response.ok) {
console.debug(response.status)
} else {
console.error(response)
}
}
const validate = (values: Object) => {
const errors: any = {}
if (!values.product_name) {
errors.product_name = "Include name"
}
return errors
}
... initialValues defined ...
const formik = useFormik({
initialValues: initialValues,
validate,
onSubmit: (values: Object, { resetForm }) => {
console.debug(JSON.stringify(values, null, 2))
handleSubmitProduct(values, resetForm)
},
})
return (
<FormLabel>Display name</FormLabel>
<TextField
onChange={formik.handleChange}
id="product_name"
onBlur={formik.handleBlur}
error={formik.touched.product_name && Boolean(formik.errors.product_name)}
helperText={formik.touched.product_name && formik.errors.product_name}
/>
<Button onClick={() => formik.handleSubmit()} variant="contained">
Submit
</Button>
)
I know there are many other questions like this but mine is different where I know the underlying Formik resources for values, errors, touched have been cleared but the values are still present in the text boxes.
The issue is I know the underlying Formik objects are cleared because after I submit, the validation triggers and prompts me like there is no value in the text field.
I've tried
resetForm({values: {initialValues}}) has the same result
resetForm(initialValues) has the same result
Use action.resetForm({values: {initialValues}}) in the onSubmit() which same result
https://codesandbox.io/s/mui-formik-fr93hm?file=/src/MyComponent.js but this approach uses the <Formik /> as opposed to useFormik which would change up my entire page but I'm in process to try anyway
I think the problem is that value of TextField is not value of formik. so the TextField is not controlled and by chaning value of formik it won't change.
assigning value of formik to it will do what you want
value={formik.values.firstName}
like this :
<TextField
onChange={formik.handleChange}
id="product_name"
value={formik.values.firstName}
onBlur={formik.handleBlur}
error={formik.touched.product_name && Boolean(formik.errors.product_name)}
helperText={formik.touched.product_name && formik.errors.product_name}
/>

Set values of list of Material UI Autocompletes that fetch options dynamically

I have a Material UI Autocomplete combo-box child component class that fetches results as the user types:
...
fetchIngredients(query) {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState({
options: options
});
});
}
...
<Autocomplete
options={this.state.options}
value={this.state.value}
onChange={(e, val) => {
this.setState({value: val});
}}
onInputChange={(event, newInputValue) => {
this.fetchIngredients(newInputValue);
}}
renderInput={(params) => {
// Hidden input so that FormData can find the value of this input.
return (<TextField {...params} label="Foo" required/>);
}}
// Required for search as you type implementations:
// https://mui.com/components/autocomplete/#search-as-you-type
filterOptions={(x) => x}
/>
...
This child component is actually rendered as one of many in a list by its parent. Now, say I want the parent component to be able to set the value of each autocomplete programmatically (e.g., to auto-populate a form). How would I go about this?
I understand I could lift the value state up to the parent component and pass it as a prop, but what about the this.state.options? In order to set a default value of the combo-box, I'd actually need to also pass a single set of options such that value is valid. This would mean moving the ajax stuff up to the parent component so that it can pass options as a prop. This is starting to get really messy as now the parent has to manage multiple sets of ajax state for a list of its Autocomplete children.
Any good ideas here? What am I missing? Thanks in advance.
If these are children components making up a form, then I would argue that hoisting the value state up to the parent component makes more sense, even if it does require work refactoring. This makes doing something with the filled-in values much easier and more organized.
Then in your parent component, you have something like this:
constructor(props) {
super(props);
this.state = {
values: [],
options: []
};
}
const fetchIngredients = (query, id) => {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState(prevState => {
...prevState,
[id]: options
});
});
}
const setValue = (newValue, id) => {
this.setState(prevState => {
...prevState,
[id]: newValue
};
}
render() {
return (
<>
...
{arrOfInputLabels.map((label, id) => (
<ChildComponent
id={id}
key={id}
value={this.state.values[id]}
options={this.state.options[id]}
fetchIngredients={fetchIngredients}
labelName={label}
/>
)}
...
</>

Validating a child input type file with react-hook-form and Yup

I'm creating a form with a file upload with help of react-hook-form and Yup. I am trying to use the register method in my child component. When passing register as a prop (destructured in curly braces) the validation and submiting doesn't work. You can always submit the form and the submitted file object is empty.
Here's a sandbox link.
There are several of problems with your code.
1- register method returns an object with these properties:
{
onChange: function(){},
onBlur:function{},
ref: function(){}
}
when you define your input like this:
<input
{...register('photo')}
...
onChange={(event) => /*something*/}
/>
actually you are overrding the onChange method which has returned from register method and react-hook-form couldn't recognize the field change event. The solution to have your own onChange alongside with react-hook-form's onChange could be something like this:
const MyComp = ()=> {
const {onChange, ...registerParams} = register('photo');
...
return (
...
<input
{...params}
...
onChange={(event) => {
// do whatever you want
onChange(event)
}}
/>
);
}
2- When you delete the photo, you are just updating your local state, and you don't update photo field, so react-hook-form doesn't realize any change in your local state.
the problems in your ImageOne component could be solved by change it like this:
function ImageOne({ register, errors }) {
const [selectedImage, setSelectedImage] = useState(null);
const { onChange, ...params } = register("photo");
return (
...
<Button
className="delete"
onClick={() => {
setSelectedImage(null);
//Make react-hook-form aware of changing photo state
onChange({ target: { name: "photo", value: [] } });
}}
>
...
<input
//name="photo"
{...params}
type="file"
accept="image/*"
id="single"
onChange={(event) => {
setSelectedImage(event.target.files[0]);
onChange(event); // calling onChange returned from register
}}
/>
...
);
}
3- Since your input type is file so, the value of your photo field has length property that you can use it to handle your validation like this:
const schema = yup.object().shape({
photo: yup
.mixed()
.test("required", "photo is required", value => value.length > 0)
.test("fileSize", "File Size is too large", (value) => {
return value.length && value[0].size <= 5242880;
})
.test("fileType", "Unsupported File Format", (value) =>{
return value.length && ["image/jpeg", "image/png", "image/jpg"].includes(value[0].type)
}
)
});
Here is the full edited version of your file.

Typescript error when typing onChange event

I declared my state like below
const [updatedStep, updateStepObj] = useState(
panel === 'add'
? new Step()
: {
...selectedStep
}
);
and I have elements like
<TextField
label="Title"
value={updatedStep.title}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateStepObj({ ...updatedStep, title: e.currentTarget.value })
}
/>
I am getting Typescript error for onChange event. How can I get rid of error?
You must put the same type of data for the onChange method as that TextField is declared, so it would be like this:
<TextField
label="Title"
value={updatedStep.title}
onChange={(e: React.FormEvent<HTMLInputElement>, newValue?: string) =>
updateStepObj({ ...updatedStep, title: e.currentTarget.value })
}
/>
Note: to forget about this mess, the best thing to do before adding your method to the event is to hover over it with the mouse and it will tell you the tipado. Then you write the same and perform the action you want.
Anyway if you want something more exact I add a demo in CodeSandbox and I'll take a look :)

Resources