I am having 3 materialUI TextFields which are render n number of times (n is an integer input from the user before rendering the form field, where I stored it in a variable named groupMembersCount).
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>
);
}
What if I filled the number (groupMembersCount) and then filled those form fields and after filling, I modified groupMembersCount (either increase or decrease), in case of increase I want the values that user filled should retain and if decreased, form fields values should clear out.
I tried passing values from groupDetails array to form in "value" prop but it shows an error because the index I an writing/passing the values initially doesn't exist at that index.
You need set value from groupDetails that was stored before using useState. Please check this example:
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 DynamicGroupMember3() {
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 noOfMember = parseInt(event.target.value);
let errors = new Array(noOfMember);
setErrorText(errors);
if (groupMembersCount > noOfMember) {
let newGroup = [];
groupDetails.map((group, index) => {
if (index < noOfMember) {
newGroup.push(group);
}
});
setGroupDetails(newGroup);
}
setGroupMembersCount(noOfMember);
}
}}/>
{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"
value={groupDetails[i] ? groupDetails[i].fullName : ''}
onChange={(event) => {
handleChange(event, i)
}}
/>
<TextField
id={`phoneNo${i + 1}`}
name="phoneNo"
variant="outlined"
margin="none"
label="Mobile Number"
value={groupDetails[i] ? groupDetails[i].phoneNo : ''}
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"
value={groupDetails[i] ? groupDetails[i].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
I'm trying to create a dynamic textfield that takes data from gql like this
const { data } = useQuery(DATA_LIST, {
variables: {
param: {
limit: 10,
offset: 0,
sortBy: 'order'
}
}
});
const [state, setState] = useState<any>([]);
useEffect(() => {
if (data) {
setState(data?.dataList?.data);
}}, [data]);
then create a textField like this :
<TextField
name="name"
required
fullWidth
// label="Status Name"
onChange={(event) => handleChange(event, index)}
value={item?.name}
sx={{ marginRight: 5 }}
/>
<TextField
name="category"
required
fullWidth
select
// label="Category"
onChange={(event) => handleChange(event, index)}
value={item?.category}
>
{Category.map((option, index) => (
<MenuItem key={index} value={option.value}>
{option.name}
</MenuItem>
))}
</TextField>
handleChange :
const handleChangeInput = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const values = [...state];
values[index][event.target.name] = event.target.value;
console.log(values[index], 'ini values');
setState(values);
};
and call the inputRow component like this (im using drag and drop for textField list) :
{state.map((item: any, index: any) => {
// console.log(statusName[index]);
return (
<Draggable key={item.id} draggableId={String(item.id)} index={index}>
{(provided, snapshot): JSX.Element => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<Box marginRight={2}>
<TypographyComponent text={index + 1} type={'subBody'} />
</Box>
<InputRow index={index} item={item} handleChange={handleChangeInput} />
</div>
)}
</Draggable>
);
})}
but when i try to type the textfield, an error appears that Cannot assign to read only property
error message
This is weird because if I input dummy data, the textfield can be modified, but if I use data from the API the data cannot be modified.
My task is I need to filter only users from the list what I am doing is I have one field called Select Name in which I have a name list so if I select any name from the list in that I have some data in that data I need to filter only users that users I need to show in another filed called username but right now what happens if I select any one name from the first field in second TextField all the data is shown but I need to show only present users in that list in handleChange function I am performing it...
export default function App() {
const AssignSearchesForm = useFormik({
initialValues: {
selectName: "",
username: []
},
onSubmit: (values) => {
console.log(values);
}
});
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.values(name).reduce((optionList, key) => {
// if(Object.keys(name) === "user"){
// optionList.push({ value: key, label: key });
// }
optionList.push({ value: key, label: key });
return optionList;
}, []);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("username", newOptions);
};
console.log(AssignSearchesForm?.values?.username);
return (
<div className="App">
<Card color="primary" variant="outlined">
<Grid
sx={{
mt: 1
}}
container
direction="row"
spacing={3}
>
<Grid item xs={4}>
<TextField
sx={{ minWidth: 170 }}
select
id="outlined-basic"
label="Select Name"
name="selectName"
size="small"
onChange={handleChange}
value={AssignSearchesForm.values.selectName}
>
{nameList?.map((option) => (
<MenuItem key={option.selectName} value={option.selectName}>
{option.selectName}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item xs={4}>
<TextField
sx={{ minWidth: 170 }}
select
id="outlined-basic"
label="username"
name="username"
size="small"
{...AssignSearchesForm.getFieldProps("username")}
>
{AssignSearchesForm?.values?.username.length ? (
AssignSearchesForm?.values?.username?.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.value}
</MenuItem>
))
) : (
<MenuItem disabled>Please first select name</MenuItem>
)}
</TextField>
</Grid>
<Grid item xs={4}>
<Button
onClick={() => {
AssignSearchesForm.handleSubmit();
}}
variant="contained"
>
Add
</Button>
</Grid>
</Grid>
</Card>
</div>
);
}
CodeSandBox Link
Please add this code in change handler.
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.values(name).reduce((optionList, key) => {
// if(Object.keys(name) === "user"){
// optionList.push({ value: key, label: key });
// }
if (key != null && key.toString().includes("user"))
optionList.push({ value: key, label: key });
return optionList;
}, []);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("username", newOptions);
};
You need to use Object.keys with filter predicate to get only those properties.
const handleChange = (e) => {
const selectedName = e.target.value;
const name = nameList.find((data) => data.selectName === selectedName);
const newOptions = Object.keys(name).filter(f => f.match(/user\d+/)).map(f => ({ value: f, label: f }));
console.log(newOptions);
AssignSearchesForm.setFieldValue("selectName", selectedName);
AssignSearchesForm.setFieldValue("username", newOptions);
};
I've used regex to match user<any digit>. You can update that regex as per your requirement.
export const data = [
{
size: "S",
colorMap: { Yellow: 10, Green: 5, Black: 50 },
productName: "Shirt",
price: 200
}
];
I wanted to show the initial values of the colorMapand then update its quantity. How can I update the quantities of the colors which are the values of the Object.entries(colorMap)?
Codesandbox: https://codesandbox.io/s/form-changehanlder-2-2repsp?file=/part2.js
The product here came from the parent component:
This is the child component
import React, { useState } from "react";
import { Grid, TextField } from "#mui/material";
const Part2 = ({ product }) => {
const [qty, setQty] = useState();
const handleSubmit = (e) => {
e.preventDefault();
console.log(qty);
};
return (
<div>
{product &&
product.map((prod, index) => (
<>
<Grid item key={index}>
<form onSubmit={handleSubmit}>
{Object.entries(prod.colorMap).map((color, index) => (
<Grid
container
rowSpacing={1}
columnSpacing={{ xs: 1, sm: 2, md: 3 }}
key={color[0]}
>
<Grid item xs={6}>
<TextField
type="text"
variant="outlined"
label="Color"
fullWidth
value={color[0]}
disabled
/>
</Grid>
<Grid item xs={6}>
<TextField
type="number"
variant="outlined"
fullWidth
label="Quantity"
value={color[1]}
onChange={(e) => console.log(index)}
/>
</Grid>
</Grid>
))}
</form>
</Grid>
</>
))}
</div>
);
};
export default Part2;
First make changeHandler in demo.js as you are using React State in demo.js so you have to make onChangeHandler in that file and pass it in props of part2.
Like:
const onChangeValues = (propertyName, index, value) => {
let item = product?.[index];
if (item) {
item.colorMap[propertyName] = value;
let prods = [...product];
prods[index] = item;
setProduct(prods);
}
};
And pass this function in props of Part2:
<Grid item>
<Part2 product={product} onChange={onChangeValues} />
</Grid>
In part2 Component you can consume it as follows:
<TextField
type="number"
variant="outlined"
fullWidth
label="Quantity"
value={color[1]}
onChange={({ target: { value } }) => {
onChangeHandler(color[0], index, value);
}}
/>
Codesandbox Link: https://codesandbox.io/s/form-changehanlder-2-forked-l9unl0?file=/part2.js:1131-1521
I have this array where it has a color and a quantity. And when saving in Firestore, it saves as a string. How can I save the colorStocks in Firestore as a number?
This is how I save this in Firestore:
const [productName, setProductName] = useState();
const [size, setSize] = useState();
const [price, setPrice] = useState();
const [colorList, setColorList] = useState([{ color: "", colorStocks: "" }]);
const handleColorChange = (e, index) => {
const { name, value } = e.target;
const list = [...colorList];
list[index] = { ...list[index] }; // copy the item too
list[index][name] = value;
setColorList(list);
};
const handleSubmit = async (e) => {
e.preventDefault();
const docRef = await addDoc(collection(db, "products"), {
prodName: productName,
size: size + "-" + size1,
colorList,
});
console.log("Document written with ID: ", docRef.id);
};
The colors text fields are dynamic, hence, I used an array:
<form onSubmit={handleSubmit}>
<Grid>
<Grid item xs>
//product textfield here
</Grid>
<Grid item xs>
//some select here
</Grid>
<Grid item xs>
size
/>
</Grid>
</Grid>
<Typography variant="h6">Colors</Typography>
{colorList.map((singleColor, index) => (
<div key={index}>
<div>
<Grid>
<TextField
label="color"
name="color"
type="text"
id="color"
required
value={singleColor.color}
onChange={(e) => handleColorChange(e, index)}
/>
</Grid>
<br />
<Grid>
<TextField
label="Stocks"
name="colorStocks"
type="text"
id="colorStocks"
required
value={singleColor.colorStocks}
onChange={(e) => handleColorChange(e, index)}
/>
</Grid>
</div>
<br />
<Grid item xs>
//button to remove color
</Grid>
<br />
//button to add color
</div>
))}
<br />
<Divider />
<br />
<Grid item xs>
<Button type="submit">Submit</Button>
</Grid>
</form>
Is there way where I could destructure colorList to save it as a Number() in firestore? Or what are the other ways this would be solved? This should be as a number since this would be subtracted later on.
The colorList in console:
Just convert colorStocks to number before sending to Firestore.
const handleColorChange = (e, index) => {
const { name, value } = e.target;
setColorList(prevState => {
let list = [...prevState];
list[index][name] = name === 'colorStocks' ? +value : value;
return list;
});
};
I have a problem fetching the shipping options, the error: 'Unhandled Rejection (TypeError): undefined is not an object (evaluating 'options[0].id')'. I have checked everything I can think of to debug this issue. I am using check.io as for my e-commerce backend. I have added different shipping option on that site however they are not showing up. I have a hard time understanding why there is a type error
import React, { useState, useEffect } from "react";
import {
InputLabel,
Select,
MenuItem,
Button,
Grid,
Typography,
} from "#material-ui/core";
import { useForm, FormProvider } from "react-hook-form";
import { Link } from "react-router-dom";
import FormInput from "../CheckoutForm/CustomTxtField";
import { commerce } from "../../lib/Commerce";
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);
setShippingOptions(options[0].id);
};
useEffect(() => {
if (checkoutToken && checkoutToken.id) {
fetchShippingCountries(checkoutToken.id);
console.log(checkoutToken.id);
}
}, [checkoutToken]);
useEffect(() => {
if (shippingCountry) fetchSubdivisions(shippingCountry);
}, [shippingCountry]);
useEffect(() => {
if (shippingSubdivision)
fetchShippingOptions(
checkoutToken.id,
shippingCountry,
shippingSubdivision
);
}, [shippingSubdivision]);
return (
<div>
<Typography variant="h6" gutterBottom>
Shipping address
</Typography>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
test({
...data,
shippingCountry,
shippingSubdivision,
shippingOption,
})
)}
>
<Grid container spacing={3}>
<FormInput name="firstName" label="First name" />
<FormInput name="lastName" label="Last name" />
<FormInput name="address1" label="Address line 1" />
<FormInput name="email" label="Email" />
<FormInput name="city" label="City" />
<FormInput 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>
</div>
);
};
export default AddressForm;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This was a commercejs.com issue not a coding issue