How should I manage multiple select fields dynamically added in React JS? - reactjs

This code adds dynamic input fields using react.
Two select dropdowns and one input text.
When clicking on add button. the same replica of these 3 input fields is added below the old input block.
When I change the value of one selected then it automatically changes other select input values. For example, you select the type of jewelry as a ring then, another type of jwellery also got reflected. I am beginner in react.
import React,{useState } from 'react'
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import InputLabel from '#material-ui/core/InputLabel';
import Select from '#material-ui/core/Select';
import { makeStyles } from "#material-ui/core/styles";
import Button from '#material-ui/core/Button';
const backgroundShape = require('./images/background.svg');
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
backgroundColor: '#064771',
overflow: 'hidden',
background: `url(${backgroundShape}) repeat`,
backgroundSize: 'cover',
backgroundPosition: '0 1000px',
paddingBottom: 500
},
action_btn:{
marginTop:'10px',
marginRight: "5px"
},
main_grid:{
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
margin:'auto',
display: 'flex',
}
}));
function App() {
const classes = useStyles();
//handle mmultiple input
const [state, setState] = React.useState({
gold_caratage: "",
gold_type: ""
});
// handle input change
const handleInputChange = (evt) => {
const value = evt.target.value;
setState({
...state,
[evt.target.name]: value
});
};
//clone form logic
const [inputList, setInputList] = useState([{ jwellary_type: "", net_gram: "",caratage:"" }]);
//remove and add btn logic
const handleRemoveClick = index => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
// handle click event of the Add button
const handleAddClick = () => {
setInputList([...inputList, { jwellary_type: "", net_gram: "",caratage:"" }]);
};
return (
<div className={classes.root}>
<typography guttorbuttom align="center">
<h1>React Calc</h1>
</typography>
{inputList.map((x, i) => {
return (
<Grid container className={classes.main_grid} spacing={3}>
<Grid item xs={10} sm={2}>
<FormControl style={{ minWidth:140 }}>
<InputLabel id="demo-simple-select-label">Type Of Jwellary</InputLabel>
<Select
name="gold_type"
value={state.gold_type}
onChange={handleInputChange}
>
<MenuItem value={1}>Ring</MenuItem>
<MenuItem value={2}>Chain</MenuItem>
<MenuItem value={3}>Other</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={10} sm={2}>
<TextField
name="Net Gram"
label="Net Gram"
type="number"
fullwidth
/>
</Grid>
<Grid item xs={10} sm={2}>
<FormControl style={{ minWidth: 120 }}>
<InputLabel id="demo-simple-select-label">Caratage</InputLabel>
<Select
value={state.gold_caratage}
onChange={handleInputChange}
name="gold_caratage"
>
<MenuItem value={1}>22</MenuItem>
<MenuItem value={2}>23</MenuItem>
<MenuItem value={3}>24</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={10} sm={2}>
<Button
variant="contained"
color="secondary"
className={classes.action_btn}
onClick={() => handleRemoveClick(i)}>
Remove
</Button>
<Button
variant="contained"
color="primary"
className={classes.action_btn}
onClick={handleAddClick}>
Add
</Button>
</Grid>
</Grid>
);
})}
</div>
)
}
export default App;

Issue
The main issue here is that you've a single state that is used for all the input values and other than index there is no way to differentiate one set of inputs from the next.
Solution
When adding new input sets to the inputList you will want to assign unique id properties to each set. This serves a couple purposes:
The id can be used as the React key for each mapped input set. This helps with rerendering and reconciliation when input sets are deleted.
You can use the id for updating and deleting state.
There is no need for the separate input state state, the inputList state has all the data necessary.
import { v4 as uuidV4 } from 'uuid';
export default function App() {
const classes = useStyles();
//clone form logic
const [inputList, setInputList] = useState([
{
id: uuidV4(), // <-- provide id
jwellary_type: "",
net_gram: "",
caratage: ""
}
]);
// handle input change
const handleInputChange = (id) => (evt) => {
const { value } = evt.target;
setInputList((list) =>
list.map((el) => // <-- shallow copy array
el.id === id // <-- match by id
? {
...el, // <-- shallow copy element
[evt.target.name]: value // <-- update key/value
}
: el // <-- or return current element
)
);
};
//remove and add btn logic
const handleRemoveClick = (id) => {
// <-- shallow copy array and remove elements with mismatch id
setInputList((list) => list.filter((el) => el.id !== id));
};
// handle click event of the Add button
const handleAddClick = () => {
setInputList([
...inputList,
{
id: uuidV4(), // <-- provide id
jwellary_type: "",
net_gram: "",
caratage: ""
}
]);
};
return (
<div className={classes.root}>
<typography guttorbuttom align="center">
<h1>React Calc</h1>
</typography>
{inputList.map((x, i) => {
return (
<Grid
key={x.id} // <-- provide id as React key
container
className={classes.main_grid}
spacing={3}
>
<Grid item xs={10} sm={2}>
<FormControl style={{ minWidth: 140 }}>
<InputLabel id="demo-simple-select-label">
Type Of Jwellary
</InputLabel>
<Select
name="jwellary_type" // <-- name to match property
value={x.jwellary_type} // <-- current property
onChange={handleInputChange(x.id)} // <-- pass id
>
<MenuItem value={1}>Ring</MenuItem>
<MenuItem value={2}>Chain</MenuItem>
<MenuItem value={3}>Other</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={10} sm={2}>
<TextField
name="Net Gram"
label="Net Gram"
type="number"
fullwidth
/>
</Grid>
<Grid item xs={10} sm={2}>
<FormControl style={{ minWidth: 120 }}>
<InputLabel id="demo-simple-select-label">Caratage</InputLabel>
<Select
value={x.caratage} // <-- current property
onChange={handleInputChange(x.id)} // <-- pass id
name="caratage" // <-- name to match property
>
<MenuItem value={1}>22</MenuItem>
<MenuItem value={2}>23</MenuItem>
<MenuItem value={3}>24</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={10} sm={2}>
<Button
variant="contained"
color="secondary"
className={classes.action_btn}
onClick={() => handleRemoveClick(x.id)} // <-- pass id
>
Remove
</Button>
<Button
variant="contained"
color="primary"
className={classes.action_btn}
onClick={handleAddClick}
>
Add
</Button>
</Grid>
</Grid>
);
})}
</div>
);
}

Related

How to get a value using formik with a MUI component?

I am using Formik in order to submit an array of components using MUI sliders.
I have created a slider component using the MUI library. You can see that in the code below in the declaration of:
import SliderSchedule from ....
So, I searched an example and I found a way to create and delete components and I copy from them. However now I want to also submit the result. But when I click the button I get no values.
As you can see I have the Comp1 that I declare the array const [sliderSchedule, setSliderSchedule]. Which has to contain values like: [(100, 200), (150, 300), (400, 700), ...].
Then I have the two function addHandeler and deleteHandeler that creates and deletes the components. This works fine.
Then we have the formik and that's the last thing that I have implemented after creating the the Comp2 that it's the "component" that we are creating with the addHandeler and deleteHandeler which creates the slider and a button to eliminate:
As you can see is very simple. And it works fine. But then when I click submit I get no values:
Thank you in advance.
import React from 'react';
import { useDispatch, useSelector } from "react-redux";
import history from "../../../history";
import { nanoid } from "nanoid";
/* MUI */
import { Container, Grid, Typography, Paper, TextField } from '#material-ui/core'
/* MUI edited Components */
import Appbar from '../../../components/Appbar';
import Button from '../../../components/Button';
import Translator from '../../../components/Translator';
import SliderSchedule from '../../../components/SliderSchedule';
/* Formik */
import { Formik, Form } from 'formik';
import * as Yup from "yup";
export default function Slots() {
const dispatch = useDispatch();
const paperStyle = { padding: 20, height: 'auto', width: 'auto', margin: "20px auto", backgroundColor: "#A9A9A9" }
function Comp2({ deleteHandeler, id, props }) {
return (
<Grid container item xs={12}>
<Grid item xs={11}> <SliderSchedule id={id} name={props.values.name} value={props.values.time} onChange={time => props.setFieldValue("time", time.target.value)} renderInput={(props) => <TextField {...props} />} /> </Grid>
<Grid item xs={1}> <Button type="button" style="Close" onClick={deleteHandeler}>CloseIcon</Button> </Grid>
</Grid>
);
}
const formSchema = {
time: []
};
function Comp1() {
const [sliderSchedule, setSliderSchedule] = React.useState([]);
const addHandeler = () => { setSliderSchedule(sliderSchedule => [...sliderSchedule, nanoid()]) };
const deleteHandeler = (removeId) => setSliderSchedule((sliderSchedule) => sliderSchedule.filter((id) => id !== removeId));
return (
<Formik
initialValues={formSchema}
onSubmit={values => {
console.log("Monday", values);
}}
render={props => (
<Form>
<Grid container justifyContent="center">
{sliderSchedule.map((id) => (<Comp2 key={id} id={id} deleteHandeler={() => deleteHandeler(id)} props={props} />))}
<Button variant="outlined" color="info" style="AddSlider" startIcon="AddIcon" onClick={addHandeler}> <Translator page="productSchedule" text="buttonAddSlider"></Translator> </Button>
<Button type="submit" variant="contained" color="primary"> Submit </Button>
</Grid>
</Form>
)}
/>
);
}
return (
<>
<Appbar></Appbar>
<Container>
<Grid container alignItems="flex-end">
{/* LA SUMA DE TOTS ELS xs={x} ha de ser xs={12} */}
<Grid container justifyContent="flex-start" item xs={12}>
<Typography variant="h3"> <Translator page="productSchedule" text="typographyTitle"></Translator> </Typography>
<Typography variant="h3"> 📅 </Typography>
</Grid>
<Grid item xs={12}>
<Paper elevation={10} style={paperStyle}>
<Grid container alignItems="flex-end">
<Grid container justifyContent="center" item xs="auto"> <Typography variant="h3"> <Translator page="productSchedule" text="typographyMonday"></Translator> </Typography> </Grid>
<Grid container justifyContent="center" item xs="auto"> <Comp1 /> </Grid>
</Grid>
</Paper>
</Grid>
</Grid>
</Container>
</>
);
}

How can I get the value of a component I created?

I created a component (it's actually a function but I think they're called components?) for a Select Field.
const useStyles = makeStyles((theme) => ({
formControl: {
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}))
export default function SelectField() {
const classes = useStyles()
const [value, setValue] = React.useState("")
return(
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="age">Age</InputLabel>
<Select
labelId="age"
id="age"
label="Age"
key="index"
value={value}
onChange={(val) => setValue(val)}
>
<MenuItem value=""><em>None</em></MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
)
}
And I'm using this Select field on another "component" (I'll only show the import for that field).
import SelectField from './fields/select'
export default function CreateGame(){
const [questions, setQuestions] = React.useState([{
question: 'asdasdasd',
answer: '',
score: '',
age: ''
}])
const HandleField = (event, index, field) => {
let temp_questions = questions
temp_questions[index][field] = event.target.value
setQuestions(temp_questions)
console.log(questions)
}
const setAge = (e, index) => {
let questions_temp = questions
questions_temp[index]['age'] = e.target.value
setQuestions(questions_temp)
console.log(questions)
}
return(
<Card>
<CardContent>
<Typography>
Add Questions
</Typography>
{questions.map((value, index) =>
<Grid container spacing={1}>
<Grid item lg={2}>
<TextField
value={value['question']}
key={index}
label='Question'
variant='outlined'
onChange={e => HandleField(e, index, 'question')} />
</Grid>
<Grid item lg={2}>
<TextField
value={value['answer']}
key={index}
label='Answer'
variant='outlined'
onChange={e => HandleField(e, index, 'answer')} />
</Grid>
<Grid item lg={2}>
<TextField
value={value['score']}
label='Score'
key={index}
variant='outlined'
onChange={HandleField} />
</Grid>
<Grid item lg={5}>
<SelectField age={value['age']} setAge={setAge} index={index} />
</Grid>
</Grid>
)}
</CardContent>
<Button>Add</Button>
</Card>
)
}
I want to get my <SelectField />'s selected value and save it in my questions state, specifically in the age field. But I don't know exactly how to capture that value. I tried putting a value prop, but I don't think that's the correct way.
Keep the state only in the parent, and pass it down as a prop, as well as another prop - a function which, when called, sets the age in the parent.
You also need to correct the shape of onChange - it accepts an argument of the event, not of the new value.
// in CreateGame
const setAge = i => (newAge) => {
setQuestions([
questions.slice(0, i),
{ ...questions[i], age: newAge },
questions.slice(i + 1),
]);
};
// ...
<Grid item lg={5}>
<SelectField age={questions.age} setAge={setAge(index)} />
</Grid>
export default function SelectField({ age, setAge }) {
// ...
<Select
value={age}
onChange={e => setAge(e.currentTarget.value)}
You can go through these steps
- Step 1 Make an onchange event listener
<SelectField onChange={handleQuestion}/>
- Step 2 Use Hooks to make a state inside functional component
const [Question, setQuestion] = useState("")
- Step 3 Make the method that you had called in step 1
const handleQuestion = () =>{
//First get the value coming fron the onchange listener
console.log(e.target.value);
//Set the state to the question
setQuestion(e.target.value)
}
- Step 4 Pass the value to the question
<TextField
value={Question}//Pass the questions state value here
/>

How to send data to Final Form

The nearest example of what I am trying to do is this
https://codesandbox.io/s/3x989zl866?file=/src/index.js
However instead of fields that people fill out, I have 3 buttons with prices: 300,500,1000
When they click on them I need to to set the value in
<TextField
name="campaignAmount"
id="campaignAmount"
component="input"
type="number"
placeholder="Total"
/>
I can set the field but it won't send the value to the final form.
My full code:
import React, { useState } from 'react';
import Wizard from '../wrappers/Wizard'
import { Form, Field } from 'react-final-form'
import ExternalModificationDetector from '../wrappers/ExternalModificationDetector';
import BooleanDecay from '../wrappers/BooleanDecay'
import {
Typography,
Paper,
Grid,
Button,
ButtonGroup,
Box,
CssBaseline,
} from '#material-ui/core';
import DateFnsUtils from '#date-io/date-fns';
import {
TextField,
Radios,
KeyboardDatePicker,
} from 'mui-rff';
const onSubmit = async values => {
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
};
// MAIN FORM AREA
export default function BuildAdStepper(props: MyFormProps) {
// const [selectedDate, handleDateChange] = useState(new Date());
const [txtValue, setTxtValue] = useState({})
const today = new Date();
const dateTimeFormat = new Intl.DateTimeFormat('en', { year: 'numeric', month: '2-digit', day: '2-digit' })
const [{ value: month },,{ value: day },,{ value: year }] = dateTimeFormat.formatToParts(today)
const nmonth = new Date(today.getFullYear(), today.getMonth()+1, day);
const [{ value: nextmonth },,{ value: nextday },,{ value: nextyear }] = dateTimeFormat.formatToParts(nmonth)
function schemaTypeSelectionHandle(event) {
console.log('key: ', event.target.parentNode.value);
setTxtValue(""+event.target.parentNode.value);
// console.log('key: ', event.target.attributes.getNamedItem('data-key').value);
}
const calculator = createDecorator({
field: /day\[\d\]/, // when a field matching this pattern changes...
updates: {
// ...update the total to the result of this function
total: (ignoredValue, allValues) =>
(allValues.day || []).reduce((sum, value) => sum + Number(value || 0), 0)
}
})
return (
<div style={{ padding: 16, margin: 'auto', maxWidth: 600 }}>
<CssBaseline />
<Typography variant="h5" align="center" component="h2" gutterBottom>
Create your campaign
</Typography>
<Paper style={{ padding: 16 }}>
<Form
onSubmit={onSubmit}
decorators={[calculator]}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<Wizard
initialValues={{promoting:"business", startDate:`${day}-${month}-${year }`,endDate:`${nextday}-${nextmonth}-${nextyear }`}}
onSubmit={onSubmit}
>
<Wizard.Page>
<Typography variant="h5">What are you Advertising</Typography>
<Box m={2}/>
<Radios
name="promoting"
formControlProps={{ margin: 'none' }}
radioGroupProps={{ row: true }}
data={[
{ label: 'Business', value: 'business' },
{ label: 'Community Project', value: 'community' },
{ label: 'Event', value: 'event' },
]}
/>
<Box m={2}/>
</Wizard.Page>
<Wizard.Page>
<Typography variant="h5">Name Your Campaign</Typography>
<Box m={2}/>
<TextField
name="campaignName"
margin="none"
fullWidth
variant="outlined"
required={true}
/>
<Box m={2}/>
</Wizard.Page>
<Wizard.Page>
<Typography variant="h5">Set your schedule and budget</Typography>
<Box m={2} />
<KeyboardDatePicker label="Start Date" format="dd-MM-yyyy" name="startDate" dateFunsUtils={DateFnsUtils} required={true} value={new Date()} />
<Box m={2} />
<KeyboardDatePicker label="End Date" format="dd-MM-yyyy" name="endDate" dateFunsUtils={DateFnsUtils} value={new Date(nextyear,nextmonth - 1,nextday)} required={true}/>
<Box m={2} />
<Typography variant="h5">Your budget will be evenly distributed between start and end dates.</Typography>
<Grid container>
<Grid item lg={6}>
<ButtonGroup color="primary" onClick={schemaTypeSelectionHandle.bind(this)} aria-label="outlined primary button group">
<Button value='300'>$300</Button>
<Button value='500'>$500</Button>
<Button value='1000'>$1000</Button>
</ButtonGroup>
</Grid>
<Grid item lg={3}>
<ExternalModificationDetector name="total">
{externallyModified => (
<BooleanDecay value={externallyModified} delay={1000}>
{highlight => (
<TextField
name="campaignAmount"
id="campaignAmount"
component="input"
type="number"
placeholder="Total"
style={{
transition: 'background 500ms ease-in-out',
background: highlight ? 'yellow' : 'none'
}}
/>
)}
</BooleanDecay>
)}
</ExternalModificationDetector>
</Grid>
</Grid>
</Wizard.Page>
</Wizard>
)}
/>
</Paper>
</div>
);
}
I have uploaded the code to codesandbox.io
But can't seem to run it.
https://codesandbox.io/s/keen-architecture-iu02l?file=/src/core/pages/BuildAd.js
If you want to set the field value using multiple buttons, you can do the following:
Add an artificial field calling, say, budget:
<Field
name="budget"
render={({ input }) => (
<button
onClick={() => input.onChange(500)}
type="button"
>
500
</button>
)}
/>
This field renders a button, which has an onClick handler that calls the field's onChange method and sets the appropriate value.
Then, if you duplicate this field like following:
<Field name="budget" render={({ input }) => (
<button onClick={() => input.onChange(500)} type="button">500</button>)}
/>
<Field name="budget" render={({ input }) => (
<button onClick={() => input.onChange(1000)} type="button">1000</button>)}
/>
<Field name="budget" render={({ input }) => (
<button onClick={() => input.onChange(10000)} type="button">10000</button>)}
/>
you would have three buttons that update the budget to 500, 1000 and 10000 accordingly.
Since you have this value in the form state, you can copy it to the campaignAmount field using a final-form-calculate decorator:
const calculator = createDecorator({
field: "budget",
updates: {
total: (_, allValues) => allValues.budget
}
});
Here is a complete codesandbox: https://codesandbox.io/s/httpsstackoverflowcomquestions63323056how-to-send-data-to-final-form-ssoz3?file=/index.js.

Material ui list of select item need to be selected

i have created selected box..using functional component...i am getting list of items from the back end, i am looping that list and showing in the front end....now i need to be selected item need to show in the box what i have selected...could any one help on this...
Please see my example code in this link https://codesandbox.io/s/gallant-water-fnqiv
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import InputLabel from "#material-ui/core/InputLabel";
import MenuItem from "#material-ui/core/MenuItem";
import FormControl from "#material-ui/core/FormControl";
import Select from "#material-ui/core/Select";
const useStyles = makeStyles(theme => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const lists = ["Ten", "twenty", "Thirty", "Fourty", "Fifity", "Sixty"];
export default function SimpleSelect() {
const classes = useStyles();
const [age, setAge] = React.useState("");
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
const handleChange = event => {
setAge(event.target.value);
};
return (
<div>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel ref={inputLabel} id="demo-simple-select-outlined-label">
Age
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={age}
onChange={handleChange}
labelWidth={labelWidth}
>
<MenuItem value={""}>
{lists.map(item => {
<li>{item.list}</li>;
})}
</MenuItem>
</Select>
</FormControl>
</div>
);
}
There is no property in the array named, item.list , so you need to use item alone which itself gives the value you want.
Also make sure <MenuItem value={item}> is inside the lists.map() method..
Include return statement of the MenuItemis inside ofthe lists.map() method which throws error in your codesandbox link
And hence,
Change:
<MenuItem value={""}>
{lists.map(item => {
<li>{item.list}</li>;
})}
</MenuItem>
To:
{lists.map(item => {
return (
<MenuItem value={item}>
<li>{item}</li>
</MenuItem>
);
})}
Forked Codesandbox
you can try this-
<div>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel ref={inputLabel} id="demo-simple-select-outlined-label">
Age
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={age}
onChange={handleChange}
labelWidth={labelWidth}
>
{lists.map(item => (
<MenuItem value={item}>{item}</MenuItem>
))}
</Select>
</FormControl>
</div>
https://codesandbox.io/s/material-ui-select-m8lgt
Updated code sandbox link
Your MenuItem was wrong.
{lists.map(item => (
<MenuItem value={item}>{item}</MenuItem>
))}

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).

Resources