specific handler method for dynamaically rendered form - javascript

I am having 3 materialUI TextFields which are render n number of times (n is an integer input from user before rendering the form field, here I stored it in a variable named groupMembersCount). I am using functional component in ReactJS defining an array with useState hooks as:
const [groupDetails, setGroupDetails] = React.useState([
{ fullName: "", phoneNo: "", gender: "" },
]);
I am getting it dynamically rendered by this way:
export default function DynamicGroupMember() {
const [groupMembersCount, setGroupMembersCount] = useState(0);
const [show, setShow] = useState(false);
const [groupDetails, setGroupDetails] = useState([
{fullName: "", phoneNo: "", gender: ""},
]);
function handleChange(event, index) {
console.log(event.target.value, index);
let newArr = [...groupDetails]; // copying the old datas array
let item = newArr[index];
item = {...item, [event.target.name]: event.target.value};
newArr[index] = item;
setGroupDetails(newArr);
}
return (
<div>
Number of Group: <TextField name="groupMembersCount" onChange={(event) => {
setGroupMembersCount(event.target.value)
}}/>
{Array.apply(null, {length: groupMembersCount}).map(
(e, i) => (
<div key={i}>
<strong>Member #{i + 1}</strong>
<div className="getIndex" name={i + 1}>
<TextField
id={`name${i + 1}`}
name="fullName"
variant="outlined"
margin="none"
label="Name"
onChange={(event) => {
handleChange(event, i)
}}
/>
<TextField
id={`phoneNo${i + 1}`}
name="phoneNo"
variant="outlined"
margin="none"
label="Mobile Number"
onChange={(event) => {
handleChange(event, i)
}}
/>
<Select
id={`gender${i + 1}`}
name="gender"
variant="outlined"
margin="none"
label="Gender"
onChange={(event) => {
handleChange(event, i)
}}
>
<option value="MALE">Male</option>
<option value="FEMALE">Female</option>
<option value="OTHER">Other</option>
</Select>
</div>
</div>
)
)}
<Button onClick={() => {
setShow(true)
}}>Show</Button>
{
show ?
groupDetails.map(member =>
<Card>
<CardContent>
<Typography color="textSecondary" gutterBottom>
{member.fullName}
</Typography>
<Typography variant="h5" component="h2">
{member.phoneNo}
</Typography>
<Typography color="textSecondary">
{member.gender}
</Typography>
</CardContent>
</Card>) : null
}
</div>
);
}
I want to make one onChange handler on a dynamically rendered form field (phoneNo) to check what user is typing, and within that handler method I will add two props "error" to make the mobile number field red, and "helperText" to display a message (eg. incorrect number, if user type alphabet in number).
now if i am checking the validation of the number field, error and helperText are appearing in all the mobile number input fields (in n times rendered rows). I want it specific to the row in which the user is entering.
I hope I explained the problem statement well, if not please ask for any information.

here is an example to validate input those are dynamically generated.
import TextField from "#material-ui/core/TextField";
import React, {useState} from "react";
import Select from "#material-ui/core/Select";
import Button from "#material-ui/core/Button";
import Card from "#material-ui/core/Card";
import CardContent from "#material-ui/core/CardContent";
import Typography from "#material-ui/core/Typography";
export default function DynamicGroupMember2() {
const [groupMembersCount, setGroupMembersCount] = useState(0);
const [show, setShow] = useState(false);
const [errorText, setErrorText] = useState([]);
const [showState, setShowState] = useState(false);
const [groupDetails, setGroupDetails] = useState([
{fullName: "", phoneNo: "", gender: ""},
]);
const [state, setState] = React.useState({
idProof: "",
noOfPeople: "",
bookingId: "",
detailsOfPeople: [],
});
function handleChange(event, index) {
event.preventDefault();
console.log(errorText.length, 'length');
if (event.target.name === "phoneNo") {
// do validation here
let valid = false;
if (isNaN(event.target.value)) {
let arr = [...errorText];
arr[index] = 'Invalid ' + event.target.name;
setErrorText(arr);
}
else {
let arr = [...errorText];
arr[index] = '';
setErrorText(arr);
}
}
let newArr = [...groupDetails]; // copying the old datas array
let item = newArr[index];
item = {...item, [event.target.name]: event.target.value};
newArr[index] = item;
setGroupDetails(newArr);
}
return (
<div>
Number of Group: <TextField name="groupMembersCount" onChange={(event) => {
if(isNaN(event.target.value)){
alert('Please enter number');
return;
}
if(event.target.value !== '') {
let errors = new Array(parseInt(event.target.value));
setErrorText(errors);
setGroupMembersCount(event.target.value);
console.log(errorText[1], 'errorText[i]')
}
}}/>
{Array.apply(null, {length: groupMembersCount}).map(
(e, i) => (
<div key={i}>
<strong>Member #{i + 1}</strong>
<div className="getIndex" name={i + 1}>
<TextField
id={`name${i + 1}`}
name="fullName"
variant="outlined"
margin="none"
label="Name"
onChange={(event) => {
handleChange(event, i)
}}
/>
<TextField
id={`phoneNo${i + 1}`}
name="phoneNo"
variant="outlined"
margin="none"
label="Mobile Number"
onChange={(event) => {
handleChange(event, i)
}}
error={errorText[i] !== '' && errorText[i] !== undefined}
helperText={errorText[i]}
/>
<Select
id={`gender${i + 1}`}
name="gender"
variant="outlined"
margin="none"
label="Gender"
onChange={(event) => {
handleChange(event, i)
}}
>
<option value="MALE">Male</option>
<option value="FEMALE">Female</option>
<option value="OTHER">Other</option>
</Select>
</div>
</div>
)
)}
<Button onClick={() => {
setShow(true)
}}>Show</Button>
{
show ?
groupDetails.map((member, index) =>
<Card key={index}>
<CardContent>
<Typography color="textSecondary" gutterBottom>
{member.fullName}
</Typography>
<Typography variant="h5" component="h2">
{member.phoneNo}
</Typography>
<Typography color="textSecondary">
{member.gender}
</Typography>
</CardContent>
</Card>) : null
}
<Button onClick={() => {
console.log(groupDetails, 'groupDetails');
setState({
idProof: "XYZ123",
noOfPeople: groupDetails.length,
bookingId: "boking-4434",
detailsOfPeople: groupDetails
});
console.log(groupDetails, 'groupDetails');
setShowState(true);
}}>Show STATE</Button>
{
showState ?
<Card>
<CardContent>
<Typography color="textSecondary" gutterBottom>
Id Proof: {state.idProof}
</Typography>
<Typography variant="h5" component="h2">
No Of People: {state.noOfPeople}
</Typography>
<Typography color="textSecondary">
Booking Id: {state.bookingId}
</Typography>
</CardContent>
</Card> : null
}
</div>
);
}

Related

React/JS if and for conditions

I am trying to create a form and store it in an useState array, every time there is a change on "maritalStatus" of the field and the status is 'Married' - I need to append the form so that the data I will store will store 2 persons details from the form (firstname/lastname/phone etc) if the status is other than 'Married' the data I store will only be for 1 person.
I am new to RN and I just can't handle this "IF" condition, would be glad if anyone can help.
Also in case the person selects - the children field and inputs a value( for example 2) the array should also be appended with 2 other fields and I will add later a fuction that will store this data on a remote server.
import React, { useState } from "react";
import {
Card,
CardContent,
Container,
Grid,
TextField,
FormControl,
InputLabel,
MenuItem,
Select,
FormControlLabel,
RadioGroup,
Radio,
FormLabel
} from '#mui/material';
import { Box } from '#mui/system';
function AddForm() {
const defaultData = {
firstName: '',
lastName:'',
email:'',
childrens:'',
gender:'Male',
participation:'Yes',
phone1:'',
phone2:'',
phone3:'',
maritalStatus:'',
}
const [applicant, setApplicant] = useState([{ ...defaultData }]);
const onChange = (e, index) => {
const { name, value } = e.target;
const data = [...applicant];
data[index][name] = value;
setApplicant(data);
};
const onChildrensChange = (e, index) => {
const { name, value } = e.target;
const data = [...applicant];
data[index][name] = value;
setApplicant(data)
setApplicant([...applicant, { ...defaultData }]);
};
let onMaritalStatusChange = (e, index) => {
const { name, value } = e.target;
const data = [...applicant];
data[index][name] = value;
setApplicant(data)
if(e.maritalStatus === 'Married'){
setApplicant([...applicant, { ...defaultData }]);
}
};
const onAddClick = () => {
setApplicant([...applicant, { ...defaultData }]);
};
return (
<Container>
<Card>
<CardContent>
{applicant.map((element, index) => {
return (
<Grid>
<Grid>
<TextField
label="First Name"
name="firstName"
value={element.firstName}
onChange={(e) => onChange(e, index)}
fullWidth
/>
<TextField
label="Last name"
name="lastName"
value={element.lastName}
onChange={(e) => onChange(e, index)}
fullWidth
/>
<TextField
label="Email"
name="email"
value={element.email}
onChange={(e) => onChange(e, index)}
fullWidth
/>
<TextField
label="childrens"
name="childrens"
value={element.childrens}
onChange={(e) => onChildrensChange(e, index)}
fullWidth
/>
<FormControl fullWidth>
<InputLabel>Gender</InputLabel>
<Select
label="gender"
name='gender'
fullWidth
onChange={(e) => onChange(e, index)}
defaultValue='Male'
>
<MenuItem value={'Male'}>Male</MenuItem>
<MenuItem value={'Female'}>Female</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth>
<InputLabel>Marital Status</InputLabel>
<Select
label="maritalStatus"
name='maritalStatus'
fullWidth
onChange={(e) => onMaritalStatusChange(e, index)}
defaultValue='Single'
>
<MenuItem value={'SINGLE'}>Single</MenuItem>
<MenuItem value={'Married'}>Married</MenuItem>
<MenuItem value={'Divorced'}>Divorced</MenuItem>
<MenuItem value={'Widowed'}>Widowed</MenuItem>
</Select>
</FormControl>
<FormControl>
<FormLabel>Participation</FormLabel>
<RadioGroup
defaultValue="female"
name="participation"
onChange={(e) => onChange(e, index)}
>
<FormControlLabel value="Yes" control={<Radio />} label="Yes" />
<FormControlLabel value="No" control={<Radio />} label="No" />
</RadioGroup>
</FormControl>
<TextField
label="Phone"
name="phone1"
value={element.phone1}
onChange={(e) => onChange(e, index)}
fullWidth
/>
<TextField
label="Phone"
name="phone2"
value={element.phone2}
onChange={(e) => onChange(e, index)}
fullWidth
/>
<TextField
label="Phone"
name="phone3"
value={element.phone3}
onChange={(e) => onChange(e, index)}
fullWidth
/>
</Grid>
</Grid>
)
})}
<Box mt={5}>{JSON.stringify(applicant)}</Box>
</CardContent>
</Card>
</Container>
)
}
export default AddForm;
import './App.css';
import AddForm from './AddForm';
function App() {
return (
<div className="App">
<AddForm/>
</div>
);
}
export default App;
Define a key like "spouse" with object type value on defaultData object.
Have the spouse form details defined just beneath the "maritalStatus" select dropdown.
Use conditional rendering for spouse form like below
{ element.maritalStatus === "Married" && (
// write the spouse form here
)}
So, as soon as you update the state with maritalStatus and it is "Married", the spouse form details would be rendered.
You can do the same for children forms too.
You are checking e. maritalStatus. Instead you should check e.target.value or, in your case, as you are already destructuring the value, you can directly check the value
if(value==="Married"){ // write your code over here}
Also to solve this problem you need to play with the Applicant state where on the basis of children and married you can show the form.
const Myform = () => {
const defaultData = {
firstName: "",
lastName: "",
email: "",
childrens: "",
gender: "Male",
participation: "Yes",
phone1: "",
phone2: "",
phone3: "",
maritalStatus: ""
};
const [applicant, setApplicant] = React.useState(defaultData);
const handleChildern = (e) => {
const udatedApplicant = { ...applicant };
udatedApplicant.childrens = e.target.value;
setApplicant({ ...udatedApplicant });
};
const handleRadio = (e) => {
const udatedApplicant = { ...applicant };
udatedApplicant.maritalStatus = e.target.value;
setApplicant({ ...udatedApplicant });
};
return (
<>
<div onChange={handleRadio}>
<label htmlFor="Married">Married</label>
<input type="radio" id="Married" name="marStatus" value="Married" />
<label htmlFor="single">Single</label>
<input type="radio" id="single" name="marStatus" value="Single" />
</div>
<div>
<label htmlFor="children">children</label>
<input type="text" id="children" onChange={handleChildern} />
</div>
{applicant.maritalStatus === "Married" && <RenderSpouse />}
{Number(applicant.childrens) > 0 && (
<RenderChildren numberOfChildren={applicant.childrens} />
)}
</>
);
};
const RenderSpouse = () => {
return <div>Hi I am spouse</div>;
};
const RenderChildren = ({ numberOfChildren }) => {
const [childrens, setChildrens] = React.useState(
new Array(Number(numberOfChildren)).fill("")
);
useEffect(() => {
const newChildren = new Array(Number(numberOfChildren)).fill("");
setChildrens(newChildren);
}, [numberOfChildren]);
return (
<>
{childrens.map((_, index) => {
return <div key={index}>children {index}</div>;
})}
</>
);
};
So with this approach your form would be more generic to solve this problem
Working Code Example

Dynamically adding more `select` and it does not show the value anymore

I was trying to duplicate the select field. However, it does not show any value anymore.
If I'll choose small for the size, this is what it shows in the console.
This is the codesandbox link: https://codesandbox.io/s/basicselect-material-demo-forked-4g34r?file=/demo.js
The codes:
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import InputLabel from "#mui/material/InputLabel";
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select from "#mui/material/Select";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [prod, setProd] = useState("");
const [qty, setQty] = useState(0);
const [design, setDesign] = useState("");
const [size, setSize] = useState("");
const handleChange = (event) => {
setProd(event.target.value);
};
const handleChangeSize = (event) => {
setSize(event.target.value);
};
const handleChangeDesign = (event) => {
setDesign(event.target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log(prod, qty, size, design);
};
const [sizeList, setSizeList] = useState([{ size: "" }]);
console.log(sizeList);
//helper method
const handleAdd = () => {
setSizeList([...sizeList, { size: "" }]);
};
const handleRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleSizeChange = (e, index) => {
const { name, value } = e.target.value;
setSize(e.target.value);
const list = [...sizeList];
list[index][name] = value;
setSizeList(list);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Product</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={prod}
label="Product"
onChange={handleChange}
>
<MenuItem value="Item1">Item1</MenuItem>
<MenuItem value="Item2">Item2</MenuItem>
<MenuItem value="Item3">Item3</MenuItem>
</Select>
</FormControl>
<br />
<br />
{/* <TextField
type="number"
label="Quantity"
variant="outlined"
value={qty}
onChange={(e) => setQty(e.target.value)}
fullWidth
/> */}
<br />
<br />
{sizeList.map((singleSize, index) => (
<div key={index}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Size</InputLabel>
<Select
labelId="demo-simple-select-label"
id="size"
value={singleSize.size}
label="Product"
// onChange={handleChangeSize}
onChange={(e) => handleSizeChange(e, index)}
>
<MenuItem value="S">Small</MenuItem>
<MenuItem value="M">Medium</MenuItem>
<MenuItem value="L">Large</MenuItem>
</Select>
</FormControl>
<br />
<br />
{sizeList.length > 1 && (
<Button
onClick={() => handleRemove(index)}
variant="contained"
color="secondary"
>
Remove{" "}
</Button>
)}
<br />
<br />
{sizeList.length - 1 === index && (
<Button variant="contained" onClick={handleAdd}>
{" "}
Add Quantity
</Button>
)}
</div>
))}
<br />
<br />
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Choose Design</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={design}
label="Product"
onChange={handleChangeDesign}
>
<MenuItem value="Design1">Design1</MenuItem>
<MenuItem value="Design2">Design2</MenuItem>
<MenuItem value="Design3">Design3</MenuItem>
</Select>
</FormControl>
<br />
<br />
<Button type="submit">Submit </Button>
</form>
<Button>Add more Product </Button>
</Box>
);
}
Any help would be appreciated. Thank you
Update:
Link: https://codesandbox.io/s/form-1-ls6rx?file=/demo.js
The value of the select now shows. But it does not update correctly in the console.
If I'll select M in the first field. The console shows {size: " "}. Adding another quantity and selecting a size. This is what console shows {size: 'M'} {size: ''}
console:
What could I use to update it according to what was selected?
The size selects don't have name props, and as the handler is specific to these selects, you can update the value of the key size. You can also use a functional state update as the new state depends on the previous state.
Updated change handler:
const handleSizeChange = (e, index) => {
const { value } = e.target;
setSizeList((prev) =>
Object.assign([...prev], { [index]: { size: value } })
);
};
Update
You can use useEffect if you want to check the sizeList after it updates:
useEffect(() => {console.log(sizeList)}, [sizeList])

Cant bind form to nested array properly in react js

I'm trying to bind to a nested array to text inputs from a form. There are 6 blocks and each block contains 3 values. I have initially populated the array for mapping by using:
new Array(6).fill(['','',''])
and rendering the values using 2 loops. One for the 6 blocks and the other 3 values inside each block. The 3 values are the ones binding to the form. I refer to each of the inputs using the second parameter of the map function which is the index.
Here is the code in its entirety.
import {
Card,
CardContent,
CardHeader,
CardMedia,
TextField,
Grid,
Typography,
Button,
} from "#material-ui/core";
import axios from "axios";
import { useState } from "react";
import { useHistory } from "react-router";
export default function CreateEditQuestion() {
const answerPack = new Array(6).fill(["", "", ""]);
const [question, setQuestion] = useState("");
const [answers, setAnswer] = useState(answerPack);
const [TimeAllowed, setTimeAllowed] = useState(30);
const [Score, setScore] = useState(1);
const [Date, setDate] = useState("");
const history = useHistory();
const handleAnswerSet = (value, answerIndex, answerTextIndex) => {
var updatedAnswer = answers;
updatedAnswer[answerIndex][answerTextIndex] = value;
setAnswer(updatedAnswer);
};
const handleSubmit = () => {
let data = {
question,
answer_set: answers,
time_allowed: TimeAllowed,
score_value: Score,
date: Date,
};
for (const [key, value] of Object.entries({ question, Date })) {
if (value.trim().length == 0) {
alert(`${key} has not been filled in`);
return false;
}
}
axios
.post(
"https://localhost:8000/question",
data
)
.then((resp) => {
alert("Succesfully added Question");
history.push("/question");
})
.catch((err) => {
console.log(err);
});
};
return (
<>
<h1>Create Question</h1>
<Card elevation={1}>
<CardHeader></CardHeader>
<CardContent>
<div>
<TextField
fullWidth
label="Question"
variant="outlined"
onChange={(e) => {
setQuestion(e.target.value);
}}
></TextField>
</div>
<Grid container direction={"row"} spacing={4}>
{answers.map((answerTexts, i) => {
return (
<Grid key={i} item md={4} width={50}>
{answerTexts.map((text, j) => {
return (
<div style={{ width: "70%", marginTop: "30px" }}>
<TextField
fullWidth
label={`Answer ${i + 1} - ${j + 1}`}
onChange={(ev) => {
handleAnswerSet(ev.target.value, i, j);
}}
variant="outlined"
/>
<br />
</div>
);
})}
</Grid>
);
})}
</Grid>
<Grid container direction={"row"} spacing={4}>
<Grid item md={5} width={80}>
<Typography variant="h6" gutterBottom gutterTop>
Extra Options
</Typography>
<TextField
label={"Time Allowed : "}
variant="outlined"
defaultValue={TimeAllowed}
onChange={(ev) => {
setTimeAllowed(ev.target.value);
}}
/>{" "}
<TextField
label="Score"
variant="outlined"
defaultValue={Score}
onChange={(ev) => {
setScore(ev.target.value);
}}
/>
</Grid>
<Grid item md={5} width={100}>
<Typography variant="h6" gutterBottom gutterTop>
Question Date
</Typography>
<TextField
type="date"
onChange={(ev) => {
setDate(ev.target.value);
}}
/>
</Grid>
</Grid>
<div align="center">
<Button
onClick={() => {
handleSubmit();
}}
variant="contained"
color="primary"
>
Submit Question
</Button>
</div>
</CardContent>
</Card>
</>
);
}
Problem:
On each block, changing any value also changes all the other corresponding inputs in other blocks, so if the first input on the first block is changed, then all other first inputs in the other blocks also get changed,e.g(changing answer 1-1 also changes 2-1,3-1,4-1, etc). I could not trace why. Only the corresponding values should be changed
This is the function responsible for setting the values.
const handleAnswerSet = (value, answerIndex, answerTextIndex) => {
var updatedAnswer = answers;
updatedAnswer[answerIndex][answerTextIndex] = value;
setAnswer(updatedAnswer);
};

Disable the next button until the form is filled using Reactjs

I need to disable next button until the form is filled out by the user. Full component is attached in here. I need to disable the next button. This form is build useFrom() in react-hook-from with material UI. This is integrated with API data for the address1, city, and the zip fields. Next button is process to the Payment gateway. So I need to disable the button until the form fields are completed. Only need to validate is all the fields are filled and once completed next is show to click.
const AddressForm = ({ checkoutToken, test }) => {
const [shippingCountries, setShippingCountries] = useState([]);
const [shippingCountry, setShippingCountry] = useState('');
const [shippingSubdivisions, setShippingSubdivisions] = useState([]);
const [shippingSubdivision, setShippingSubdivision] = useState('');
const [shippingOptions, setShippingOptions] = useState([]);
const [shippingOption, setShippingOption] = useState('');
const methods = useForm();
const fetchShippingCountries = async (checkoutTokenId) => {
const { countries } = await commerce.services.localeListShippingCountries(checkoutTokenId);
setShippingCountries(countries);
setShippingCountry(Object.keys(countries)[0]);
};
const fetchSubdivisions = async (countryCode) => {
const { subdivisions } = await commerce.services.localeListSubdivisions(countryCode);
setShippingSubdivisions(subdivisions);
setShippingSubdivision(Object.keys(subdivisions)[0]);
};
const fetchShippingOptions = async (checkoutTokenId, country, stateProvince = null) => {
const options = await commerce.checkout.getShippingOptions(checkoutTokenId, { country, region: stateProvince });
setShippingOptions(options);
setShippingOption(options[0].id);
};
useEffect(() => {
fetchShippingCountries(checkoutToken.id);
}, []);
useEffect(() => {
if (shippingCountry) fetchSubdivisions(shippingCountry);
}, [shippingCountry]);
useEffect(() => {
if (shippingSubdivision) fetchShippingOptions(checkoutToken.id, shippingCountry, shippingSubdivision);
}, [shippingSubdivision]);
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="contained" to="/cart" color="secondary">Back to Cart</Button>
<Button type="submit" variant="contained" color="primary">Next</Button>
</div>
</form>
</FormProvider>
</>
);
};
You can use a valid function as mentioned before
const isValid = shippingCountries && shippingCountry && shippingSubdivisions && shippingSubdivision && shippingOptions && shippingOption
<Button disabled={!isValid} />
As you use FormProvider and useForm() i think you use react-hook-form? If yes it is even more easy because it has an integrated validator.
For example you could use it like that
const { register, handleSubmit, setValue } = useForm();
....
<form onSubmit={methods.handleSubmit((data) => test({ ...data, shippingCountry, shippingSubdivision, shippingOption }))}>
<FormInput {...register("firstName, {required: true})} />
</form
With setValue you can set the values of the form from external (for example your useEffect function)
setValue([{ firstName: name }, { secondOption: option }]);
reference code:
<Button type="submit" variant="contained" color="primary" disabled={!this.state.shippingCountry || !this.state.shippingSubdivision || ...}>Next
PS: you can have a function which return true or false for the above and then enable...

React js #material-ui/core Select Cannot read property 'offsetWidth' of null

I'm using the Select component on #material-ui/core, but I'm having the following problem:
Cannot read property 'offsetWidth' of null
Can you help me out?
Link: codesandbox
Code:
import React from "react";
import {
Button,
DialogTitle,
Dialog,
DialogContent,
DialogActions,
TextField,
FormControl,
InputLabel,
MenuItem,
Select
} from "#material-ui/core";
function AddUserModal(props) {
const inputLabelTypeRole = React.useRef(null);
const { open, onClose } = props;
const [state, setState] = React.useState({
labelWidthTypeRole: 0,
name: ""
});
let { labelWidthTypeRole } = state;
React.useEffect(() => {
setState({
...state,
labelWidthTypeRole: inputLabelTypeRole.current.offsetWidth
});
}, []);
const onChange = name => ({ target: { value } }) => {
setState({
...state,
[name]: value
});
};
const [currency, setCurrency] = React.useState(false);
const handleChange = event => {
setCurrency(event.target.value);
};
return (
<Dialog
fullWidth
maxWidth="md"
open={open}
onClose={onClose}
aria-labelledby="max-width-dialog-title"
>
<DialogTitle id="form-dialog-title" style={{ alignSelf: "center" }}>
Add User
</DialogTitle>
<DialogContent>
<div style={{ margin: 5 }}>
<TextField
margin="dense"
id="name"
label="Name"
type="Name"
fullWidth
variant="outlined"
onChange={onChange("name")}
/>
</div>
<div style={{ margin: 5 }}>
<FormControl variant="outlined" fullWidth size="small">
<InputLabel
ref={inputLabelTypeRole}
id="demo-simple-select-outlined-label"
>
Role
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
labelWidth={labelWidthTypeRole}
value={false}
>
<MenuItem value={false}>Report</MenuItem>
<MenuItem value>Report Web hook</MenuItem>
</Select>
</FormControl>
</div>
<div style={{ margin: 5 }}>
<TextField
id="outlined-select-currency"
select
label="Select"
helperText="Please select your currency"
variant="outlined"
fullWidth
value={currency}
onChange={handleChange}
>
<MenuItem value={false}>Report</MenuItem>
<MenuItem value>Report Web hook</MenuItem>
</TextField>
</div>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" variant="contained">
Create
</Button>
</DialogActions>
</Dialog>
);
}
export default function App() {
const [open, setOpen] = React.useState(false);
return (
<div className="App">
<AddUserModal open={open} onClose={() => setOpen(false)} />
<button onClick={() => setOpen(true)}>Open</button>
</div>
);
}
The error is resolved very simply: remove "current" in useEffect hook:
React.useEffect(() => {
setState({
...state,
labelWidthTypeRole: inputLabelTypeRole.**current**.offsetWidth
});
}, []);
Because in the example that you used there are several components, but you have one component and "current" is superfluous.

Categories