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

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.

Related

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}
/>

React JS - disable a button if the input field is empty?

React Code:
import { useState } from "react";
export default function IndexPage() {
const [text, setText] = useState("");
const autoComplete = (e) => {
setText({ value: e.target.value });
};
return (
<form>
<input type="text" placeholder="search here" onChange={autoComplete} />
<button type="submit" disabled={!text}> find </button>
</form>
);
}
SandBox Link: https://codesandbox.io/s/practical-panini-slpll
Problem:
when Page Loads, the find button is disabled at first because the input field is empty which is good. But,
Now, when I start typing something. then delete them all with Backspace key. the find button is not disabling again.
I want:
I just want to have my find button disabled if the input field is empty
Where I'm doing wrong?
It's a common mistake coming from Class Components, state setter in hooks does not merge state value as in classes.
You initialized your state as a string (useState('')) and then assigned it as an object
const autoComplete = (e) => {
setText({ value: e.target.value });
};
// State is an object { value: 'some text' }
Fix your set state call:
const autoComplete = (e) => {
setText(e.target.value);
};
// State is a string 'some text'
Then to be more verbose you want to check if the string is empty:
<button disabled={text === ''}>find</button>
disabled={!text.value}
Should do the trick.
With this function
const autoComplete = (e) => {
setText({ value: e.target.value });
};
you are writing your input in text.value, not in text.
use this code for check text
<button type="submit" disabled={text==''}> find </button>

How to reset the value of antd autocomplete, if the value is not in the data source

I'm using ant design AutoComplete for a location selection. And the data source is an array. Here is my code.
<Form.Item
label={t("City")}
name="location"
rules={[
{
required: true,
message: t("Please enter your city"),
},
]}
>
<AutoComplete
options={this.props.cityList}
allowClear={true}
placeholder={t("Enter your city")}
className="ant-select-custom"
filterOption={(inputValue, option) =>
option.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
}
/>
</Form.Item>
The problem is, I can type anything that not in the data source. What I want it to restrict that. If I type something that not in the data source, The value should be erased. I tried with onBlur() and onChange() but no luck. Can anyone help me with this?
Thanks in advance
set value props in AutoComplete tag, and set an state for it, check if value on search is not found change the state to the initial.
const [value, setValue] = useState('');
const handleSearch = async val => {
if (val) {
const response = await dispatch(yourAction(val));
if (!response?.length) {
...
setValue(''); // this will clear the input
} else {
...
}
} else {
...
}
};
<AutoComplete
...,
value={value}
/>
This works for me.

Changing the label of required in text-field with react and material ui

I am using Material-UI in react application. What I am trying to do is to change the label "Please fill out this field" of text-field when we set the required attribute.
I tried to use setCustomValidity with inputProps, but nothing happens.
There are two types of this label. One is a tooltip that showed up when the mouse is hovered on the text-field, the other when we submit the form.
The error message you are getting is not material-ui, but is the browser handling the error. This cannot be changed since the browser renders this based on the "required" attribute. You can only change this by doing custom error handling on your form.
Here is the code snippet which i used in my personal project:
<TextField
error={this.props.error_val
? true
: false
}
required={this.props.isRequired ? true : false}
type={this.props.type}
label={this.props.label}
variant={this.props.VARIANT}
className={classes.root}
/>
You can use required and error attribute combination for deciding whether the input is filled or not.
Secondly, you can write a validate() which is basically a switch statement where you will pass "label name", "value" and put the return value into your <TextField/> component.
Snippet:
validate = (name, value) => {
let error = "";
switch (name) {
case "name":
error = (!value || value.length == 0) && "Please enter the name";
break;
}
return error;
}
You can replace the 'required' validation message for the TextField component with a custom-validation message (when submitting the form) as follows:
<TextField
onInvalid={() => {
document
.getElementById("business-email-textfield")
.setCustomValidity("Please type a valid email address");
}}
required
id="business-email-textfield"
label="Business Email"
name="email"
onChange={handleInput}
variant="standard"
/>
In the handleInput function you should handle the input of course, but you should also reset the custom-validation message. For example:
const [formInput, setFormInput] = useReducer(
(state, newState) => ({ ...state, ...newState }),
{
email: "",
businessType: "",
}
);
const handleInput = (evt) => {
const name = evt.target.name;
const newValue = evt.target.value;
setFormInput({ [name]: newValue });
//the line below clears the custom-validatio message
document.getElementById(evt.target.id).setCustomValidity("");
};

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