Dynamically adding or removing items within a react-hook-forms form and registering the inputs? - reactjs

I'm trying to implement a rather complicated form that has and date picker and input which the user can add multiple of (or delete). That data then gets added to the overall form on submit. How do I get react-hook-forms to register that little dynamic faux form within the real form?
Here's the faux form inputs:
<AddPackagesStyle>
<Label htmlFor="addedPackages" label="Add Packages" />
<DatePicker
id="dateRange"
selected={startDate}
selectsRange
startDate={startDate}
endDate={endDate}
placeholderText="select dates"
onChange={onDateChange}
/>
<PackageInput
id="PackageSelect"
placeholder="Select Package"
type="text"
value={name}
// #ts-ignore
onChange={(e) =>
// #ts-ignore
setName(e.target.value)
}
/>
<ButtonContainer>
<button type="button" onClick={clearAll}>
Clear
</button>
<button
type="button"
// #ts-ignore
onClick={addPackage}
>
Add
</button>
</ButtonContainer>
</AddPackagesStyle>
These entries get added to an array in a useState hook:
const [addedPackages, setAddedPackages] = useState<any[]>([])
Then this gets rendered in JSX as the add packages:
<ContentSubscriptionWrapper>
{addedPackages.length !== 0 &&
addedPackages.map((addedPackage, idx) => (
// #ts-ignore
<>
<ContentSubscriptionColumn>
{addedPackage.name && addedPackage.name}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.startDate &&
addedPackage.startDate.toString()}
</ContentSubscriptionColumn>
<ContentSubscriptionColumn>
{addedPackage.endDate && addedPackage.endDate.toString()}
</ContentSubscriptionColumn>
<button type="button" onClick={() => removePackage(idx)}>
X
</button>
</>
))}
</ContentSubscriptionWrapper>
So before the form gets submitted, the 'add packages' has to be set. Where do I add the {...register} object to add to the larger form object for submission?
const {
control,
register,
handleSubmit,
formState: { errors },
} = useForm<any>()
const onSubmit = (data: any) => {
console.log(data)
}

I created a CodeSandbox trying to reproduce your use case and used Material UI to get it done quickly, but you should get the idea and can modify it with your own components.
you should let RHF handle all the state of your form
use RHF's useFieldArray for managing (add, remove) your packages/subscriptions - there is no need to use watch here
use a separate useForm for your <AddPackage /> component, this has the benefit that you will have form validation for this sub form (in case it should be a requirement that all fields of <AddPackage /> need to be required) - i added validation in the demo to demonstrate this
AddPackage.tsx
export const AddSubscription: React.FC<AddSubscriptionProps> = ({ onAdd }) => {
const {
control,
reset,
handleSubmit,
formState: { errors }
} = useForm<Subscription>({
defaultValues: { from: null, to: null, package: null }
});
const clear = () => reset();
const add = handleSubmit((subscription: Subscription) => {
onAdd(subscription);
clear();
});
return (
<Card variant="outlined">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Grid container spacing={1} p={2}>
<Grid item container spacing={1} xs={12}>
<Grid item xs={6}>
<Controller
name="from"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="From"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.from}
helperText={errors.from?.message}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
name="to"
control={control}
rules={{ required: "Required" }}
render={({ field }) => (
<DatePicker
{...field}
label="To"
renderInput={(params) => (
<TextField
{...params}
fullWidth
error={!!errors.to}
helperText={errors.to?.message}
/>
)}
/>
)}
/>
</Grid>
</Grid>
<Grid item xs={12}>
<Controller
name="package"
control={control}
rules={{ required: "Required" }}
render={({ field: { onChange, ...field } }) => (
<Autocomplete
{...field}
options={packages}
onChange={(e, v) => onChange(v)}
renderInput={(params) => (
<TextField
{...params}
label="Package"
fullWidth
error={!!errors.package}
helperText={errors.package && "Required"}
/>
)}
/>
)}
/>
</Grid>
<Grid item xs={12}>
<Stack spacing={1} direction="row" justifyContent="end">
<Button variant="outlined" onClick={clear}>
Clear
</Button>
<Button variant="contained" onClick={add} type="submit">
Add
</Button>
</Stack>
</Grid>
</Grid>
</LocalizationProvider>
</Card>
);
};
Form.tsx
export default function Form() {
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: {
seats: "",
addOns: false
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "subscriptions"
});
const onSubmit = (data) => console.log("data", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box display="flex" justifyContent="end" gap={1}>
<Button variant="outlined">Cancel</Button>
<Button variant="contained" type="submit">
Save
</Button>
</Box>
</Grid>
<Grid item xs={12}>
<Controller
name="seats"
control={control}
render={({ field }) => (
<TextField {...field} fullWidth label="Seats" />
)}
/>
</Grid>
<Grid item xs={12}>
<AddSubscription onAdd={append} />
</Grid>
<Grid item xs={12}>
<List>
{fields.map((field, index) => (
<ListItem
key={field.id}
secondaryAction={
<IconButton
edge="end"
aria-label="delete"
onClick={() => remove(index)}
>
<DeleteIcon />
</IconButton>
}
>
<ListItemText
primary={field.package.label}
secondary={
<span>
{formatDate(field.from)} - {formatDate(field.to)}
</span>
}
/>
</ListItem>
))}
</List>
</Grid>
<Grid item xs={12}>
<Controller
name="addOns"
control={control}
render={({ field: { value, onChange } }) => (
<FormControlLabel
control={<Checkbox checked={!!value} onChange={onChange} />}
label="Add-ons"
/>
)}
/>
</Grid>
</Grid>
</form>
);
}

Related

React JS Typescript input onChange and onFocus error

I want to implement the following repo available on Github using ReactJS Typescript. I created 3 different methods as onChange, changeHandler, focusHandler but they are not working correctly. When I click on the Card Number field, I get a 'number.replace' error.
I need to fix onChange and onFocus methods in TextField fields.
https://github.com/amaroteam/react-credit-cards
import React, {useState, FocusEvent, ChangeEvent} from 'react'
import Cards from 'react-credit-cards';
import 'react-credit-cards/es/styles-compiled.css'
const PaymentMethodEditor = () => {
const [stateList, setState] = React.useState(initialValues)
const handleFormSubmit = async (values: any) => {
console.log(values)
}
const focusHandler = (event: React.FocusEvent<HTMLInputElement>) => {
setState({ focus: event.target.name })
}
const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setState({ [name]: event.target.value })
}
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.currentTarget.value;
setState({ [name]: event.target.value })
}
return (
<CustomerDashboardLayout>
<DashboardPageHeader
icon={CreditCard}
title={`${id === 'add' ? 'Add New' : 'Edit'} Payment Method`}
button={
<Link href="/payment-methods">
<Button color="primary" sx={{ bgcolor: 'primary.light', px: '2rem' }}>
Back to Payment Methods
</Button>
</Link>
}
/>
<div id="PaymentForm">
<Cards
cvc={stateList.cvc}
expiry={stateList.exp}
focused={stateList.focus}
name={stateList.name}
number={stateList.card_no}
/>
<Card1>
<Formik
initialValues={initialValues}
validationSchema={checkoutSchema}
onSubmit={handleFormSubmit}
>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Box mb={4}>
<Grid container spacing={3}>
<Grid item md={6} xs={12}>
<TextField
name="card_name"
label="Card Name"
fullWidth
onBlur={handleBlur}
onChange={changeHandler}
onFocus={focusHandler}
value={values.card_name || ''}
error={!!touched.card_name && !!errors.card_name}
helperText={touched.card_name && errors.card_name}
/>
</Grid>
<Grid item md={6} xs={12}>
<TextField
name="card_no"
label="Card Number"
fullWidth
onBlur={handleBlur}
onChange={changeHandler}
onFocus={focusHandler}
value={values.card_no || ''}
error={!!touched.card_no && !!errors.card_no}
helperText={touched.card_no && errors.card_no}
/>
</Grid>
<Grid item md={6} xs={12}>
<TextField
name="name"
label="Name on Card"
fullWidth
onBlur={handleBlur}
onChange={handleChange}
value={values.name || ''}
error={!!touched.name && !!errors.name}
helperText={touched.name && errors.name}
/>
</Grid>
<Grid item md={6} xs={12}>
<TextField
name="exp"
label="Exp. Date"
fullWidth
onBlur={handleBlur}
onChange={handleChange}
value={values.exp || ''}
error={!!touched.exp && !!errors.exp}
helperText={touched.exp && errors.exp}
/>
</Grid>
<Grid item md={6} xs={12}>
<TextField
name="cvc"
label="CVC"
fullWidth
onBlur={handleBlur}
onChange={handleChange}
value={values.cvc || ''}
error={!!touched.cvc && !!errors.cvc}
helperText={touched.cvc && errors.cvc}
/>
</Grid>
</Grid>
</Box>
<Button type="submit" variant="contained" color="primary">
Save Changes
</Button>
</form>
)}
</Formik>
</Card1>
</div>
</CustomerDashboardLayout>
)
}
const initialValues = {
card_no: '',
name: '',
exp: '',
cvc: '',
focus: '',
number: '',
card_name: '',
}
export default PaymentMethodEditor
enter image description here
enter image description here

how can I change the layout of this material UI form?

I have a checkout form which I made using material UI looking like this:
It seems minor but I'd like to change the layout so that First Name and Last Name etc. are all on the same row, looking more like this (but I'm not sure how to go about it):
Here's the code for my address form component:
return (
<>
<Typography variant="h6" gutterBottom>
Shipping address
</Typography>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
test({
...data,
shippingCountry,
shippingSubdivision,
shippingOption,
})
)}
>
<Grid container spacing={3}>
<FormInput required name="firstName" label="First name" />
<FormInput required name="lastName" label="Last name" />
<FormInput required name="address1" label="Address line 1" />
<FormInput required name="email" label="Email" />
<FormInput required name="city" label="City" />
<FormInput required name="zip" label="Zip / Postal code" />
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Country</InputLabel>
<Select
value={shippingCountry}
fullWidth
onChange={(e) => setShippingCountry(e.target.value)}
>
{Object.entries(shippingCountries)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Subdivision</InputLabel>
<Select
value={shippingSubdivision}
fullWidth
onChange={(e) => setShippingSubdivision(e.target.value)}
>
{Object.entries(shippingSubdivisions)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Options</InputLabel>
<Select
value={shippingOption}
fullWidth
onChange={(e) => setShippingOption(e.target.value)}
>
{shippingOptions
.map((sO) => ({
id: sO.id,
label: `${sO.description} - (${sO.price.formatted_with_symbol})`,
}))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
</Grid>
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Button component={Link} variant="outlined" to="/cart">
Back to Cart
</Button>
<Button type="submit" variant="contained" color="primary">
Next
</Button>
</div>
</form>
</FormProvider>
</>
);
};
And my custom text field component:
import React from "react";
import { TextField, Grid } from "#material-ui/core";
import { useFormContext, Controller } from "react-hook-form";
const FormInput = ({ name, label, required }) => {
const { control } = useFormContext();
const isError = false;
return (
<>
<Controller
control={control}
name={name}
render={({ field }) => <TextField fullWidth label={label} required />}
/>
</>
);
};
export default FormInput;
Looks like you're already using Mui Grid so I think you need to place your <FormInput/> components within a <Grid item/> component. Like this:
<Grid container>
<Grid container direction="row">
<Grid item xs={6} sm={6}>
<FormInput required name="firstName" label="First name" />
</Grid>
<Grid item xs={6} sm={6}>
<FormInput required name="lastName" label="Last name" />
</Grid>
</Grid>
...Another Grid row, and so on...
</Grid>

React Material UI + Formik FieldArray Autocomplete control value stays same on remove

I want use Formik's form validation and it actually works just fine, but I ran into some issues with selected value display in Autocomplete component. I Have created Add/Remove buttons to dynamically adjust how many rows are in my form. The bug occurs when I try to remove form row, the row below, behind scenes has proper values as displayed in DEBUG, but user's input displays value from deleted form row. I cannot figure out, how to display or handle this occurrence.
Form before removal,
Form after removal
<FieldArray name="actions"
render={arrayHelpers =>
values.actions.map((action, index) => (
<Grid item container spacing={1} justify="center" alignItems="center"
key={index}>
<Grid item xs={4}>
<Field
error={getIn(errors, `actions.${index}.suggestedAction`) &&
getIn(touched, `actions.${index}.suggestedAction`)}
helperText={<ErrorMessage
name={`actions.${index}.suggestedAction`}/>}
name={`actions.${index}.suggestedAction`}
id={`actions.${index}.suggestedAction`}
variant="outlined"
fullWidth
as={TextField}
label="Siūloma priemonė"
multiline
rows={3}
rowsMax={10}
/>
</Grid>
<Grid item xs={4}>
<Autocomplete
freeSolo
onBlur={handleBlur}
onChange={(e, value) => {
//If adding new
if (value && value.inputValue) {
setOpen(true);
setFieldValue(`actions.${index}.responsiblePerson`, value.inputValue)
} else if (value && value.id) {
//Selecting existing
setFieldValue(`actions.${index}.responsiblePerson`, value.id)
} else {
setFieldValue(`actions.${index}.responsiblePerson`, "")
}
}}
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option;
}
if (option.inputValue) {
return option.inputValue;
}
return option.title;
}}
handleHomeEndKeys
clearText="Pašalinti"
noOptionsText="Tuščia..."
renderOption={option => option.title}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
title: `Pridėti "${params.inputValue}"`,
});
}
return filtered;
}}
renderInput={params => (
<TextField
{...params}
id={`actions.${index}.responsiblePerson`}
name={`actions.${index}.responsiblePerson`}
error={getIn(errors, `actions.${index}.responsiblePerson`) &&
getIn(touched, `actions.${index}.responsiblePerson`)}
helperText={<ErrorMessage
name={`actions.${index}.responsiblePerson`}/>}
onChange={handleChange}
variant="outlined"
label="Atsakingas asmuo"
placeholder="Vardenis Pavardenis"
/>
)}
options={top100Films}/>
</Grid>
<DateTimeUtilsProvider>
<Grid item xs={3}>
<Field
disableToolbar
as={KeyboardDatePicker}
variant="inline"
inputVariant="outlined"
format="yyyy-MM-dd"
id={`actions.${index}.deadline`}
name={`actions.${index}.deadline`}
error={getIn(errors, `actions.${index}.deadline`) &&
getIn(touched, `actions.${index}.deadline`)}
helperText={<ErrorMessage
name={`actions.${index}.deadline`}/>}
label="Įvykdymo terminas"
onChange={value =>
setFieldValue(`actions.${index}.deadline`, value)}
/>
</Grid>
</DateTimeUtilsProvider>
<Grid item xs={1}>
<ButtonGroup fullWidth orientation="vertical" size="medium">
<Button onClick={() => {
arrayHelpers.remove(index);
}}
disabled={values.actions.length === 1}
classes={removeButtonClasses}>
<HighlightOff/>
</Button>
<Button onClick={() => {
arrayHelpers.insert(index + 1, {
suggestedAction: "",
responsiblePerson: "",
deadline: Date.now()
})
}}
color="primary">
<AddCircleOutline/>
</Button>
</ButtonGroup>
</Grid>
</Grid>
))
}
/>
</Grid>
Instead of
arrayHelpers.insert()
I have used
arrayHelpers.push()
and its working fine for me.
I had this same problem. I was setting a value prop on the <Field> inside my renderInput.
<Autocomplete
renderInput={params => (
<Field {...params} component={TextField} value={values.myArray[index]} />
)}
/>
I was able to fix it by moving the value attribute to the Autocomplete.
<Autocomplete
value={values.myArray[index]}
renderInput={params => (
<Field {...params} component={TextField} />
)}
...
/>
This worked for me
const arrayValue = options.filter(
(item) => item.id === values[arrayName][index][name]);
And then I used the filtered option as my value in the Autocomplete component
<Autocomplete
name={name}
value={arrayValue.length > 0 ? arrayValue[0] : null}
options={options}
groupBy={(option) => option.group}
getOptionLabel={(option) => option.value}
isOptionEqualToValue={(option, value) => option?.id === value?.id}
defaultValue={defaultValueCheck()}
onChange={(_, value) => {
setFieldValue(`${arrayName}[${index}].${name}`, value?.id ?? "");
}}
renderInput={(params) => (
<TextField
{...params}
{...configTextField}
name={`${arrayName}[${index}].${name}`}
/>
)}
renderGroup={(params) => (
<li key={params.key}>
<GroupHeader>{params.group}</GroupHeader>
<GroupItems>{params.children}</GroupItems>
</li>
)}
/>
</>

Can't add items to material-ui form

I got handed down a project from someone else and I need to add some items to a form created with react and material-ui. I can get text fields to work but if I try to add a dropdown, it doesn't hold the value when selected.
I followed the same conventions of adding the items to the form that the guy who created this form used.
I added a grid item with a formcontrol component inside of it. There was also defaultprops set with a "game" object inside, that had all of the form fields as properties. I added the new field items to there also.
Here is the whole form component because I'm not really sure where the problem actually is.
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import history from '~/utils/history';
import { FormControl, Button, TextField, Grid, MenuItem, FormHelperText } from '#material-ui/core';
import { DateTimePicker } from 'material-ui-pickers';
import gameState from '~/utils/enums';
import { dateTime } from '~/utils/formats';
import { getById, create, update, remove } from '~/services/games';
import { getAll as getLeagues } from '~/services/leagues';
import { getAll as getSeasons } from '~/services/seasons';
import { getAll as getTeams } from '~/services/teams';
const GameForm = ({ game, id }) => {
useEffect(() => {
console.log({ form, game });
});
const [edit, setEdit] = useState(false);
const [form, setForm] = useState(game);
const [error, setError] = useState('');
const [variant, setVariant] = useState('outlined');
const [leagues, setLeagues] = useState([]);
const [seasons, setSeasons] = useState([]);
const [teams, setTeams] = useState([]);
const [gametype, setGametype] = useState(1);
const gametypes = [
{ key: 0, value: 'Series Game' },
{ key: 1, value: 'Playoff' },
{ key: 2, value: 'Final' },
{ key: 3, value: 'Else' }
];
useEffect(() => {
getSeasons()
.then(({ data }) => {
setSeasons(data);
return getLeagues();
})
.then(({ data }) => {
setLeagues(data);
if (id) {
getById(id).then(({ data }) => setForm(data));
} else {
setEdit(true);
}
});
}, []);
useEffect(() => {
if (edit) {
setVariant('outlined');
} else {
setVariant('standard');
}
}, [edit]);
useEffect(() => {
if (form.league) {
getTeams({ leagues: [form.league] }).then(({ data }) => setTeams(data));
}
}, [form.league]);
useEffect(() => {
if (form.gametype) {
setGametype('Playoff');
}
}, [form.gametype]);
const handleChange = ({ target }) => {
let { name, value } = target;
setForm({
...form,
[name]: value
});
};
const handleDateChange = formInputName => {
const dateHandler = moment => {
setForm({
...form,
[formInputName]: moment ? moment.toDate() : null
});
};
return dateHandler;
};
const handleFormSubmit = () => {
if (!edit) {
setEdit(true);
} else {
new Promise(resolve => {
resolve(form._id ? update(form) : create(form));
})
.then(() => {
history.push('/games');
})
.catch(error => setError(error.response.data.error));
}
};
const handleDelete = () => {
if (window.confirm(`Delete permanently?`)) {
remove(id).then(() => history.goBack());
}
};
const handleCancel = () => {
if (edit && id) {
setEdit(false);
} else {
history.goBack();
}
};
return (
<Grid container spacing={8} justify="space-between">
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
required
select
id="season"
name="season"
label="Season"
variant={variant}
disabled={!edit}
value={form.season}
onChange={handleChange}
>
{seasons.map(option => (
<MenuItem key={option.name} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
required
select
id="league"
name="league"
label="League"
variant={variant}
disabled={!edit}
value={form.league}
onChange={handleChange}
>
{leagues.map(option => (
<MenuItem key={option.name} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl margin="normal" required fullWidth variant="outlined">
<DateTimePicker
required
id="start"
name="start"
label="Game starts"
variant={variant}
disabled={!edit || !form.season}
autoOk
ampm={false}
keyboard
clearable
minDate={form.season ? seasons.find(({ _id }) => _id === form.season).start : undefined}
minDateMessage="Cannot start before the season begins"
maxDate={form.season ? seasons.find(({ _id }) => _id === form.season).end : undefined}
maxDateMessage="Cannot start after the end of the season"
value={form.start}
onChange={handleDateChange('start')}
{...dateTime}
/>
<FormHelperText disabled={!form.season}>
{!form.season ? 'Select season first' : undefined}
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
required
select
id="home"
name="home"
label="Home team"
variant={variant}
disabled={!edit || !form.league}
helperText={!form.league ? 'Select league first' : undefined}
value={form.home}
onChange={handleChange}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{teams
.filter(({ _id }) => _id !== form.away)
.map(option => (
<MenuItem key={option.name} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
required
select
id="away"
name="away"
label="Team away"
variant={variant}
disabled={!edit || !form.league}
helperText={!form.league ? 'Select league first' : undefined}
value={form.away}
onChange={handleChange}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{teams
.filter(({ _id }) => _id !== form.home)
.map(option => (
<MenuItem key={option.name} value={option._id}>
{option.name}
</MenuItem>
))}
</TextField>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
required
select
id="gametype"
label=" Game type"
variant={variant}
disabled={!edit}
value={form.gametype}
onChange={handleChange}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{gametypes.map(option => (
<MenuItem key={option.key} value={option.value}>
{option.value}
</MenuItem>
))}
</TextField>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
text
id="umpire"
label="Umpire"
variant={variant}
disabled={!edit || !form.league}
/>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
text
id="scorekeeper"
label="Scorekeeper"
variant={variant}
disabled={!edit || !form.league}
/>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
text
id="referee1"
label="Referee 1"
variant={variant}
disabled={!edit || !form.league}
/>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
text
id="referee2"
label="Referee 2"
variant={variant}
disabled={!edit || !form.league}
/>
</FormControl>
</Grid>
<Grid item xs={12} md={6}>
<FormControl margin="normal" required fullWidth variant="outlined">
<TextField
text
id="referee3"
label="Referee 3"
variant={variant}
disabled={!edit || !form.league}
/>
</FormControl>
</Grid>
<Grid item xs={edit && id ? 4 : 6}>
<Button variant="outlined" color="secondary" fullWidth onClick={handleCancel}>
{edit && id ? 'Cancel' : 'Back'}
</Button>
</Grid>
{edit && id ? (
<Grid item xs={4}>
<Button
type="submit"
variant="contained"
color="secondary"
fullWidth
onClick={handleDelete}
>
Delete
</Button>
</Grid>
) : null}
<Grid item xs={edit && id ? 4 : 6}>
<Button
type="submit"
variant="outlined"
color="primary"
fullWidth
onClick={handleFormSubmit}
>
{edit ? (id ? 'Save' : 'Create') : 'Edit Game'}
</Button>
</Grid>
{!edit ? (
<Grid item xs={12}>
<Button
variant="outlined"
color="primary"
fullWidth
onClick={() => {
history.push('/games/' + id + '/scores');
}}
>
EDIT SCORES
</Button>
</Grid>
) : null}
</Grid>
);
};
GameForm.defaultProps = {
game: {
season: '',
league: '',
start: null,
home: '',
away: '',
gametype: '',
umpire: '',
scorekeeper: '',
referee1: '',
referee2: '',
referee3: ''
}
};
GameForm.propTypes = {
//classes: PropTypes.object.isRequired,
game: PropTypes.object
};
export default GameForm;
I noticed that if I print the game object to the console, it only shows the old form items and not the new ones I added. The new items are: gametype, umpire, scorekeeper, referee1-3.
I've been trying to make this work for a few days already, so any help would really be appreciated.
you need onChange={handleChange} in those and you also need to provide the name as props
BTW, I highly recommend you to combine those useStates as one single state object
For example, an alternative banned multiple useState() calls in a component. You’d keep state in one object.
function Form() {
const [state, setState] = useState({
edit: '',
form: '',
error: '',
});
// ...
}
To be clear, Hooks do allow this style. You don’t have to split your state into a bunch of state variables (see doc).

formik fields are not getting value

I'm using Formik in my React and redux project, but Formik fields are not getting value and onChange field function not working!
this form is for editing a customer. I'm using Formik in another part of project and i don't have a problem. I don't know what to do?
<Formik
initialValues={props.selectedCustomer.id ? props.selectedCustomer : emptyCustomer}
validationSchema={customerValidationSchema}
onSubmit={async (values: ICustomer, actions: FormikActions<any>) => {
console.log(values)
try {
await postCustomer(values)
props.selectedCustomer.id ? enqueueSnackbar('success') : enqueueSnackbar('fail')
actions.resetForm()
getCustomers(pageNum, pageSize)
setAddDialog(false)
}
catch (error) {
enqueueSnackbar(error, { variant: 'default' })
}
}}
>
{() => (
<Form id="addCustomerForm">
<div id="addCustomerDiv" className={clsx({
[classes.noDisplay]: !addDialog
})}>
<Grid style={{marginRight: '0.5rem'}} container spacing={5}>
<Grid item xs={2}>
<Field
name="firstName"
render={({ field, form }: FieldProps<ICustomer>) => (
<TextField
margin="dense"
id="firstName"
label="firstName"
fullWidth
{...field}
error={form.errors.firstName && form.touched.firstName ? true : false}
/>
)}
/>
</Grid>
<Button
type='submit'
form="addCustomerForm"
color="primary"
variant="contained"
style={{marginTop: '3rem'}}
className={clsx({
[classes.noDisplay]: !addDialog
})}
>
submit
</Button>
</Grid>
</Grid>
</div>
</Form>
)}
</Formik>
what is the problem?
I added enableReinitialize in Formik component and it worked.

Resources