How to set custom warning with validation in React Hook Form? - reactjs

If validation fails, so id is not githubid1, or githubid2 then I would show a custom message like Please set an existing GihHub ID. Is it possible to set it?
<FormControl fullWidth sx={{ mb: 6 }}>
<Controller
name="username"
control={control}
rules={{
validate: (v) => { // <-----------
return v == "githubid1" || v == "githubid2";
},
}}
render={({ field: { value, onChange } }) => (
<TextField
value={value}
label="Username"
onChange={onChange}
placeholder="johndoe"
error={Boolean(errors.username)}
/>
)}
/>
{errors.username && (
<FormHelperText sx={{ color: "error.main" }}>
{errors.username.message}
</FormHelperText>
)}
</FormControl>

const [customError, setCustomError] = useState('');
<FormControl fullWidth sx={{ mb: 6 }}>
<Controller
name="username"
control={control}
rules={{
validate: (v) => {
if (v !== "githubid1" || v !== "githubid2") {
setCustomError('Your custom text here');
}
return v == "githubid1" || v == "githubid2";
},
}}
render={({ field: { value, onChange } }) => (
<TextField
value={value}
label="Username"
onChange={onChange}
placeholder="johndoe"
error={Boolean(errors.username)}
/>
)}
/>
{errors.username || customError && (
<FormHelperText sx={{ color: "error.main" }}>
{errors.username.message || customError}
</FormHelperText>
)}
</FormControl>

Related

Targeting 1 Textfield to make its width larger

I have created a form where I am using mui textfields to build the form. I want my textField for the description to be double the width of all the other textFields.
How can I achieve this? To only target one of the textFields
I want to target the last textField in the form and I want that one to be double the width
Below is my code for my form:
import { makeStyles, TextField } from '#material-ui/core';
import { Formik, FormikHelpers } from 'formik';
const useStyles = makeStyles(() => ({
input: {
fontFamily: 'nunito, sans-serif',
color: 'black',
border: '1px solid #393939',
borderRadius: '5px',
marginBottom: '5px',
width: '300px',
padding: '7px',
},
}));
return (
<Formik
initialValues={initialValues}
validationSchema={editUserValidation}
validateOnBlur
validateOnChange={false}
onSubmit={_handleSubmission}
enableReinitialize
>
{({
handleSubmit,
isSubmitting,
values,
touched,
errors,
handleChange,
handleBlur,
handleReset,
}) => (
<form onReset={handleReset} onSubmit={handleSubmit} className="edit-form-body">
<div className="flex">
<TextField
id="firstName"
placeholder="First Name"
type="string"
value={values.firstName}
error={touched.firstName && !!errors.firstName}
helperText={touched.firstName ? errors.firstName : ''}
onChange={handleChange('firstName')}
onBlur={handleBlur('firstName')}
InputProps={{ className: classes.input }}
/>
<div className="mx-5" />
<TextField
id="lastName"
placeholder="Last Name"
type="string"
value={values.lastName}
error={touched.lastName && !!errors.lastName}
helperText={touched.lastName ? errors.lastName : ''}
onChange={handleChange('lastName')}
onBlur={handleBlur('lastName')}
InputProps={{ className: classes.input }}
/>
</div>
<div className="flex">
<TextField
id="email"
placeholder="Email"
type="email"
value={values.email}
error={touched.email && !!errors.email}
helperText={touched.email ? errors.email : ''}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
InputProps={{ className: classes.input }}
/>
<div className="mx-5" />
<TextField
id="mobileNumber"
placeholder="Contact No."
type="string"
value={values.mobileNumber}
error={touched.mobileNumber && !!errors.mobileNumber}
helperText={touched.mobileNumber ? errors.mobileNumber : ''}
onChange={handleChange('mobileNumber')}
onBlur={handleBlur('mobileNumber')}
InputProps={{ className: classes.input }}
/>
</div>
<div className="w-full">
<TextField
id="description"
placeholder="Description"
type="string"
value={values.description}
error={touched.description && !!errors.description}
helperText={touched.description ? errors.description : ''}
onChange={handleChange('description')}
onBlur={handleBlur('description')}
InputProps={{ className: classes.input }}
multiline
maxRows={10}
minRows={5}
/>
</div>
<div className="flex justify-end">
<Button
isLoading={isSubmitting}
onClick={handleSubmit}
className="font-nunito font-bold edit-user-button mt-4"
>
SAVE
</Button>
</div>
</form>
)}
</Formik>
);
};

How to set default value in Autocomplete Material UI?

In a React project, I have an Autocomplete component which has country name and its relevant calling codes displayed in drop-down list. When selected on list renders the desired data, but, when refreshed, data is null and doesn't show the default data.
const [newValue, setNewValue] = useState({});
const [textToggle, textToggleState] = useState(false);
render(
<div
style={{ cursor: "pointer" }}
onClick={() => {
textToggleState(!textToggle);
}}
>
<h5>+{newValue == null ? "91" : newValue.calling_code}</h5> {/* <-- Value gets null when refreshed */}
</div>
{textToggle ? (
<Autocomplete
id="size-small-standard"
size="small"
options={cities}
onChange={(event, value) => {
setNewValue(value);
textToggleState(!textToggle);
}}
autoSelect={true}
getOptionLabel={(option) =>
`${option.country}` + `+ ${option.calling_code}`
}
renderOption={(option) => (
<>{`${option.country} + ${option.calling_code}`}</>
)}
//defaultValue={cities[98]}
style={{ width: "100%" }}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder="Search your country"
style={{ width: "40%" }}
/>
)}
/>
) : (
""
)}
)
Following is the CodeSandbox link: https://codesandbox.io/s/how-to-add-only-single-value-from-autocomplete-in-material-ui-forked-tu218
You can pass a value prop to autocomplete component with the newValue. But newValue is an empty object by default that doesn't exist in the options array you are passing to the autocomplete so it will show undefined + undefined in the default case. You can make that null and add null check there in the autocomplete itself or you can assign a value directly in your useState So as you are having a check for null and using 91 code so instead you can assign that value to the newValue itself directly. Check the code below I have added the value field here
<Autocomplete
id="size-small-standard"
size="small"
options={cities}
value={newValue !== null ? newValue : cities[98]}
onChange={(event, value) => {
setNewValue(value);
textToggleState(!textToggle);
}}
autoSelect={true}
getOptionLabel={(option) =>
`${option.country}` + `+ ${option.calling_code}`
}
renderOption={(option) => (
<>{`${option.country} + ${option.calling_code}`}</>
)}
//defaultValue={cities[98]}
style={{ width: "100%" }}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder="Search your country"
style={{ width: "40%" }}
/>
)}
/>
Or you can just pass newValue to the value field and assign the default value in the newValue useState as shown below
const [newValue, setNewValue] = useState(cities[98]);
.
.
.
.
<Autocomplete
id="size-small-standard"
size="small"
options={cities}
value={newValue}
onChange={(event, value) => {
setNewValue(value);
textToggleState(!textToggle);
}}
autoSelect={true}
getOptionLabel={(option) =>
`${option.country}` + `+ ${option.calling_code}`
}
renderOption={(option) => (
<>{`${option.country} + ${option.calling_code}`}</>
)}
//defaultValue={cities[98]}
style={{ width: "100%" }}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder="Search your country"
style={{ width: "40%" }}
/>
)}
/>

How to align Material ui textfields in React?

I am developing a React js application using Material ui components.
I am a 2 column grid, each column has 4 textField.
Some of those textField are weapped in Autocomplete component.
The problem is that they don't have the same styles thus they are not aligned facing each others.
Here is a screenshot of the result
As you can notice the problem in the second and third row of the left column.
Here is my code
<Grid container style={{ width: "100%", marginTop: 10 }}>
<Grid item style={{ padding: 10 }} xs={12} sm={12} md={6}>
<TextField
className={`${classes.textfield} ${
values.nameError && classes.inputError
}`}
onFocus={(e) => setValues({ ...values, nameError: false })}
onChange={(e) => setValues({ ...values, name: e.target.value })}
error={values.nameError}
helperText={values.nameError ? t("Obligatoire") : ""}
margin="normal"
fullWidth
type="text"
value={values.name}
label={t("nomFR") + " *"}
autoComplete="given-name"
/>
//Same for the rest
</Grid>
<Grid item style={{ padding: 10 }} xs={12} sm={12} md={6}>
<SelectInputWrapper>
<FormControl fullWidth>
<Autocomplete
options={spaces}
getOptionLabel={(option) => option.label}
renderOption={(option, { selected }) => (
<div className={classes.menuItem}>{option.label} </div>
)}
value={values.espace}
disableClearable
renderInput={(params) => (
<TextField
{...params}
error={values.espaceError}
onFocus={(e) =>
setValues({ ...values, espaceError: false })
}
style={{ marginLeft: 3, marginRight: 3 }}
margin="normal"
value={""}
label={t("Espace") + " *"}
/>
)}
/>
{values.espaceError && (
<FormHelperText className={classes.helper} error={true}>
{t("Obligatoire")}
</FormHelperText>
)}
</FormControl>
</SelectInputWrapper>
//Same for the rest
</Grid>
</Grid>
Notice, I am not giving any margins or paddings.
Is there a solution please? I am stuck here for 2 days.

How to set multiple FormItems under Form.List in antd V4

Single FormItem Demo in antd:https://ant.design/components/form/#components-form-demo-dynamic-form-item. There only one FormItem generated dynamically everytime.
And, here is
But I always got some errors like wrong interaction, wrong validation or wrong form value.
My code:
<Form.List name="passenger">
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? 'Passengers' : ''}
required={false}
key={field.key}
>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: "Please input passenger's name or delete this field.",
},
]}
noStyle
>
<Input placeholder="passenger name" style={{ width: '40%' }} />
</Form.Item>
<Form.Item
name={['age', index]}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: "Please input passenger's age or delete this field.",
},
]}
noStyle
>
<Input placeholder="passenger age" style={{ width: '40%', marginRight: 8 }} />
</Form.Item>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
onClick={() => {
remove(field.name);
}}
/>
) : null}
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: '60%' }}
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
From the example provided by the antd team given by #Sushilzzz:
The trick is to use field.name in the name props of the Form.Item. You don't have to nest the sub items in another item.
<Form.List name="users">
{(fields, { add, remove }) => (
<div>
{fields.map((field) => (
<Form.Item name={[field.name, "name"]}>
<Input placeholder="Name" />
</Form.Item>
...

How to update state of a component which uses two context consumers

I have a class component which uses two contexts with the value generated from API calls to a REST API.
What I want to do is get the context values and use them to update my component state.
I'm passing the context values like so
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => {
const text = textContext.text;
const user = userConsumer.user;
if(text != null && user != null){
return (
<div className="md:flex max-w-2xl">
<div className="flex flex-col flex-1 md:pr-32">
<FuseAnimateGroup
enter={{
animation: "transition.slideUpBigIn"
}}
>
<div style={{paddingRight:"8px"}}>
<Typography variant="h4" >{text.TITLE_PAGE_PROFILE}</Typography>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.PROFILE_EMAIL_PLACEHOLDER}
value = {user.email}
disabled
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<EmailIcon/>
</IconButton>
</InputAdornment>
)
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.PROFILE_NAME}
value={user.name_user}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
)
}}
/>
</form>
</div>
<div style={{paddingRight:"8px"}}>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.address_user}
placeholder={text.PROFILE_ADDRESS_PLACEHOLDER}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.city_user}
label={text.PROFILE_CITY_PLACEHOLDER}
className={classes.textField}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
}}
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
value={user.country_user}
label={text.PROFILE_COUNTRY_PLACEHOLDER}
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
}}
fullWidth
style={{ margin: 8, paddingRight: 8}}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
margin="normal"
variant="outlined"
/>
</div>
<div style={{padding:"10px"}}>
<Fab variant="contained" aria-label="delete" className={classes.fab}>
{text.PROFILE_CHANGE_PASSWORD_BUTTON_PLACEHOLDER}
</Fab>
</div>
<div style={{paddingRight:"8px"}}>
<Typography variant="h4" > {text.COMPANY_INFORMATION_TITLE}</Typography>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
placeholder={text.COMPANY_NAME_PLACEHOLDER}
value={user.name_company}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingLeft:"10px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
style={divStyle}
id="outlined"
label={text.COMPANY_EU_VAT_PLACEHOLDER}
value={user.vat_company}
className={classes.textField}
margin="normal"
variant="outlined"
/>
<TextField
style={div2Style}
id="outlined"
label={text.COMPANY_NUMBER_PLACEHOLDER}
value={user.registration_number_company}
className={classes.textField}
margin="normal"
variant="outlined"
/>
</form>
</div>
<div style={{paddingRight:"8px"}}>
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
value={user.address_company}
placeholder={text.COMPANY_ADDRESS_PLACEHOLDER}
fullWidth
margin="normal"
variant="outlined"
InputLabelProps={{
shrink: true,
}}
/>
</div>
<div style={{paddingRight:"8px"}}>
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="outlined-full-width"
style={{ margin: 8 }}
label={text.COMPANY_CITY_PLACEHOLDER}
value={user.city_company}
className={classes.textField}
fullWidth
margin="normal"
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<LocationCityIcon/>
</InputAdornment>
)
}}
/>
</form>
</div>
<div>
<TextField
id="outlined-select-currency"
select
label={text.COMPANY_COUNTRY_PLACEHOLDER}
fullWidth
style={{ margin: 8, paddingRight: 8}}
SelectProps={{
MenuProps: {
className: classes.menu,
},
}}
InputProps={{
endAdornment: (
<InputAdornment>
<IconButton>
<FlagIcon/>
</IconButton>
</InputAdornment>
)
}}
margin="normal"
variant="outlined"
/>
</div>
</FuseAnimateGroup>
</div>
<div className="flex flex-col md:w-320">
<FuseAnimateGroup
enter={{
animation: "transition.slideUpBigIn"
}}
>
<Button variant="contained" size="large" color="default" className={classes.button}>
{text.UPDATE_BUTTON_TEXT}
</Button>
</FuseAnimateGroup>
</div>
</div>
);
} else return <div>Loading...</div>
}
}
</UserContext.Consumer>
)}
</TextContext.Consumer>
I've tried to update the state inside the render by doing something like this
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => {
const text = textContext.text;
const user = userConsumer.user;
this.setState({
user:user,
text: text,
})
</UserContext.Consumer>
)}
</TextContext.Consumer>
The problem with this approach is that it throws the "Maximum update depth exceeded." error.
How should I go about this?
"Maximum update depth exceeded." error.
Do not setState() inside render().
How should I go about this?
Simply extract a component from it.
const User = (props) => {
return (
<>
<span>{props.user}</span>
<span>{props.text}</span>
</>
);
}
// in render
<TextContext.Consumer>
{(textContext) => (
<UserContext.Consumer>
{(userConsumer) => (
<User
text={textContext.text}
user={userConsumer.user}
/>
))}
</UserContext.Consumer>
)}
</TextContext.Consumer>
<User /> will still re-render every time the props (user, text) changes.
you can't update the state inside the render function.
Like that, you will be in the infinity loop of renders. Whenever you change the state that triggers the render function then you change the state again and so on.
anyway, you don't need to store this state inside the local state to use it, you can use it directly from the context.
First of all - are you sure you really need to store context in state? I don't see any reason to copy context (which always available) to state. Just use values from context, not from state.
But if you really need it, you can't update state in render function, because it will cause the infinite update loop. There some options to do so:
Extract component:
return (
<TextContext.Consumer>
{({ text }) => (
<UserContext.Consumer>
({user}) => <ExtractedComponent text={text} user={user} />
</UserContext.Consumer>
)}
</TextContext.Consumer>
);
Then you just need to overrider getDerrivedStateFromProps() for ExtractedComponent to get new state when props changed.
[ugly way] perform conditional update in render function to prevent infinite loop:
if (state.user !== user || state.text !== text) {
this.setState({ user, text });
}
Probably you can switch to functional components with hooks:
const YourComponent = () => {
const { user } = useContext(UserContext);
const { text } = useContext(TextContext);
const [ state, setState ] = useState({});
useEffect(() => {
setState({ user, text });
}, [ user, text ]);
}

Resources