I have an application where users can add their data in a form with 2 fields. They can add as many fields as they want.
const Demo = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
const firstDefaultOpen = {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
};
const testHandler = a => {
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
return (
<Form name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
<Form.List name="users">
{(fields, { add, remove }) => {
return (
<div>
{testHandler(fields).map(field => (
<Space
key={field.key}
style={{ display: "flex", marginBottom: 8 }}
align="start"
>
<Form.Item
{...field}
name={[field.name, "first"]}
fieldKey={[field.fieldKey, "first"]}
rules={[{ required: true, message: "Missing first name" }]}
>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item
{...field}
name={[field.name, "last"]}
fieldKey={[field.fieldKey, "last"]}
rules={[{ required: true, message: "Missing last name" }]}
>
<Input placeholder="Last Name" />
</Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(field.name);
}}
/>
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
block
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
My target is to set a default pair of inputs as opened. Now you can see when you open the application that first name and last name inputs are open as default. This i made with:
const firstDefaultOpen = {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
};
const testHandler = a => {
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
and here i map() the new array:
{testHandler(fields).map(field => (...
The issue is when i click on Add field button, because there when i try to write something in one input also the same text appears on the another. This is happening because when i click on Add field button you can see in the console.log("result", a.concat(firstDefaultOpen));, that 2 objects are with the same values like:
[Object, Object]
0: Object
name: 0
key: 0
isListField: true
fieldKey: 0
1: Object
name: 0
key: 0
isListField: true
fieldKey: 0
Question: How to set the first object with all values 0, and the next values to be higher, and to get something like?:
[Object, Object]
0: Object
name: 0
key: 0
isListField: true
fieldKey: 0
1: Object
name: 1
key: 1
isListField: true
fieldKey: 1
2: Object
name: 2
key: 2
isListField: true
fieldKey: 2
...
demo: https://codesandbox.io/s/hungry-star-nu5ld?file=/index.js:783-819
You can do this by just simply changing your firstDefaultOption from variable to function like this
function firstDefaultOption(optionVal){
return {
name: optionVal,
key: optionVal,
isListField: optionVal,
fieldKey: optionVal
}
}
and then changing your test handler like this
const testHandler = a => {
console.log("A",a)
console.log("result", a.concat(firstDefaultOption(a.length)));
return a.concat(firstDefaultOption(a.length));
};
Kind of a messy solution but it works... Comments added throughout
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Input, Button, Space } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "#ant-design/icons";
const Demo = () => {
const onFinish = values => {
console.log("Received values of form:", values);
};
const firstDefaultOpen = {
//Set default to null
name: null,
key: null,
isListField: true,
fieldKey: null
};
const testHandler = a => {
//Check for null and set first to 0
if(firstDefaultOpen.name == null){
firstDefaultOpen.name = 0;
firstDefaultOpen.key = 0;
firstDefaultOpen.fieldKey = 0;
}
//If not null.. then add 1 to each object item to make each unique
else{
firstDefaultOpen.name = firstDefaultOpen.name +1;
firstDefaultOpen.key = firstDefaultOpen.key +1;
firstDefaultOpen.fieldKey = firstDefaultOpen.fieldKey +1;
}
console.log("result", a.concat(firstDefaultOpen));
return a.concat(firstDefaultOpen);
};
return (
<Form name="dynamic_form_nest_item" onFinish={onFinish} autoComplete="off">
<Form.List name="users">
{(fields, { add, remove }) => {
return (
<div>
{testHandler(fields).map(field => (
<Space
key={field.key}
style={{ display: "flex", marginBottom: 8 }}
align="start"
>
<Form.Item
{...field}
name={[field.name, "first"]}
fieldKey={[field.fieldKey, "first"]}
rules={[{ required: true, message: "Missing first name" }]}
>
<Input placeholder="First Name" />
</Form.Item>
<Form.Item
{...field}
name={[field.name, "last"]}
fieldKey={[field.fieldKey, "last"]}
rules={[{ required: true, message: "Missing last name" }]}
>
<Input placeholder="Last Name" />
</Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(field.name);
}}
/>
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
block
>
<PlusOutlined /> Add field
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
ReactDOM.render(<Demo />, document.getElementById("container"));
You could declare the defaultOpen object with the let keyword and invoke an "update" function whenever you're ready to display an updated version, like:
let defaultOpen = getFirstDefaultOpen();
log(defaultOpen);
updateDefaultOpen(defaultOpen);
log(defaultOpen);
updateDefaultOpen(defaultOpen);
log(defaultOpen);
function updateDefaultOpen(currentState){
currentState.name++;
currentState.key++;
currentState.fieldKey++;
}
function getFirstDefaultOpen(){
return {
name: 0,
key: 0,
isListField: true,
fieldKey: 0
}
}
function log(currentState){
const output = Object.keys(currentState).reduce((str, key)=>{
str += `${key}:${currentState[key]}, `;
return str;
},"");
console.log(`{ ${output}}`);
}
Related
i stumbled into an issue i cant solve, i have an object 'customerDraft' which has nested object in it. i want to render every field plus the fields which are inside of 'customerDraft.metadata'.
my component looks like this:
const CustomerDetailEditModal = (props) => {
const {
open,
setOpen,
customerDraft,
customerProfileDraft,
setDraftCustomer,
setDraftProfile,
onUpdate
} = props;
const classes = useStyles();
const dispatch = useDispatch();
const [isPasswordHidden, setIsPasswordHidden] = useState(true);
// const [attributes, setAttributes] = useState({});
const projectId = useSelector(({ project }) => project.currentProject._id);
const generatedPassword = useSelector(({ customer }) => customer.password);
const isCurrentProjectCapstone = projectId === '4387564328756435';
const onModalCancel = () => {
setOpen(false);
if (isCurrentProjectCapstone) {
dispatch(removeItemFromCustomerDraftAction('password'));
}
};
const generatePassword = () => {
dispatch(getGeneratedPassword());
};
useEffect(() => {
if (!generatedPassword) return;
setDraftCustomer({
...customerDraft,
password: generatedPassword
});
// eslint-disable-next-line
}, [generatedPassword]);
console.log(customerDraft);
return (
<div>
<Modal
bodyStyle={{
fontSize: '12px',
height: 500,
margin: '0 auto'
}}
centered
footer={
<div
style={{
display: 'flex',
justifyContent: 'flex-end'
}}>
<CButton
htmlType="submit"
onClick={(e) => {
setOpen(false);
e.preventDefault();
}}
size="large"
type="secondary">
Cancel
</CButton>
<CButton
htmlType="submit"
onClick={onUpdate}
size="large"
type="primary"
// disabled={!isSaveEnabled}
>
Save
</CButton>
</div>
}
onCancel={onModalCancel}
title={
<span
style={{
fontSize: '24px',
fontWeight: 700,
lineHeight: '24px'
}}>
Edit User
</span>
}
visible={open}
width={customerProfileDraft ? 770 : 385}>
<form className={classes.form} id="customer-edit-form">
<div className={classes.wrapperDiv}>
{Object.entries(customerDraft).map((item, i) => {
if (customerDraft.fullName) {
if (restrictedData.includes(item[0] || item[0].toLowerCase().includes('id'))) {
return false;
}
}
if (restrictedData.includes(item[0]) || item[0].toLowerCase().includes('id')) {
return false;
}
return (
<CStandardInput
key={i}
allowClear
defaultValue={item[1]}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${item[0]}`}
id={`standard-customer-edit-${item[0]}`}
label={item[0]}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value
});
}}
size="large"
/>
);
})}
{isCurrentProjectCapstone && (
<div className={classes.passwordWrapper}>
<CStandardInput
adornment={
<>
<button
className={classes.buttonSvg}
onClick={() => {
navigator.clipboard.writeText(customerDraft.password || '');
}}
style={{
marginRight: '5px'
}}
type="button">
<img alt="copy password" src={copyIcon} />
</button>
<button
className={classes.buttonSvg}
onClick={() => setIsPasswordHidden(!isPasswordHidden)}
type="button">
<img
alt="toggle password visibility"
src={isPasswordHidden ? crossedEyeIcon : eyeIcon}
/>
</button>
</>
}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor="standard-input-user-password"
id="standard-input-user-password"
label="Password"
onChange={(e) => setDraftCustomer({ ...customerDraft, password: e.target.value })}
size="large"
type={isPasswordHidden ? 'password' : 'text'}
value={customerDraft.password || ''}
width="true"
/>
<CButton
onClick={generatePassword}
type="primary"
xstyle={{
borderRadius: '12px',
margin: '16px 0px 0px 16px'
}}>
Generate
</CButton>
</div>
)}
</div>
</form>
</Modal>
</div>
);
};
export default CustomerDetailEditModal;
notice how metdata field is rendered? i want to use recursion to output every field which metadata contains,
i know recursion but what i cant seem to figure out is where should this component call itself to do it.
any help with explanation so that i can understand the answer would be much appreciated!
this is the object im iterating on:
const customerData = {
createdAt: "2022-10-28T08:42:08.015Z",
email: "company#gmail.com",
firstName: "$$$$$$$",
fullName: "$$$$$$",
idNumber: "2813921321",
isEmailVerified: true,
isPhoneVerified: true,
lastName: "$$$$$",
metadata: {
birthDate: "2000-08-19 00:00:00.000",
gender: "Male",,
region: "",
status: "Adult",
statusExtra: "Student",
},
phone: "######",
project: "hlkjhkljhkjhk",
updatedAt: "2022-11-01T10:26:32.677Z",
username: null,
_id: "hlkjhlkjhlkjhlkjhlkjh",
};
see metadata? currently im outputting only the fields of the main(parent) object, but i also want to output the data which is contained in the 'metadata' key using recursion.
A solution to this could be to check if the key item[0] is "metadata". Then you could do the same as you did with the customerDraft object. Get the entries an map over them.
Note that I destructured the array you get from the .entries to make it more explicit what the variables are.
if (item[0] === "metadata") {
const inputs = Object.entries(item[1]).map(([metaKey, metaValue]) => (
<CStandardInput
key={metaKey}
allowClear
defaultValue={metaValue}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${metaKey}`}
id={`standard-customer-edit-${metaKey}`}
label={metaKey}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
));
return <>{inputs}</>;
}
return (
<CStandardInput
...
EDIT:
To support the nested data with recursion, I've created a function with returns an input for every key, value pair in the data.
You can add your extra if statements as desired
const renderInputs = (data) => {
const inputs = Object.entries(data).map(([key, value]) => {
if (
typeof value === "object" &&
!Array.isArray(value) &&
value !== null
) {
return renderInputs(value);
}
return (
<CStandardInput
key={key}
allowClear
defaultValue={value}
disableUnderline
formclasses={{ root: classes.root }}
htmlFor={`standard-customer-edit-${key}`}
id={`standard-customer-edit-${key}`}
label={key}
onChange={(event) => {
setDraftCustomer({
...customerDraft,
fullName: event.target.value,
});
setDraftProfile({
...customerProfileDraft,
fullName: event.target.value,
});
}}
size="large"
/>
);
});
return inputs;
};
return <>{renderInputs(customerData)}</>;
Hope this helps you with your project!
I am setting the selectedType variable and opening ant design Modal containing ant design Form with initialValues filled with selectedType.
But the initialValues in the form are only working for the first time I click on Edit button and if I close the Modal and click on Edit button for a different selectedType the Modal shows the values of first time selected selectedType.
I have used destroyOnClose={true} for Modal to fetch the new value of selectedType but it doesn't solve the issue.
Here's my code:
import {
Button, Form,
Input,Table,
Modal
} from "antd";
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
const ManageTypeScreen = () => {
const dispatch = useDispatch();
const productTypeList = useSelector(state => state.productTypeList);
const [editForm] = Form.useForm();
const [selectedType, setselectedType] = useState({});
const [showEditModal, setshowEditModal] = useState(false);
useEffect(() => {
getProductTypeList(dispatch);
}, [])
const handleEditClose = () => {
setshowEditModal(false);
};
const handleEditShow = (val) => {
console.log(val);
setselectedType(val);
setshowEditModal(true);
};
const columns = [
{
title: 'Type Name',
dataIndex: 'name',
key: 'name'
},
{
title: 'Short Cut',
dataIndex: 'shortCut',
key: 'shortCut'
},
{
title: 'Edit',
sortable: false,
filterable: false,
render: (text, pro) => (
<div>
<Button type="primary" size="sm block" onClick={() => handleEditShow(pro)}>
Edit
</Button>
</div>)
},
];
return (
{selectedType !== undefined &&
<Modal
title="Edit Type"
visible={showEditModal}
onCancel={handleEditClose}
destroyOnClose={true}
footer={null}
centered={true}
>
<Form
labelCol={{ span: 9 }}
wrapperCol={{ span: 12 }}
layout="horizontal"
form={editForm}
requiredMark={true}
initialValues={selectedType}
size="medium"
>
<Form.Item
label="Type Name:"
name="name"
rules={[
{ required: true, message: "Please input Name!" }
]}>
<Input maxLength={32} />
</Form.Item>
<Form.Item
label="ShortCut"
name="shortCut"
rules={[
{
required: true,
message: "Please input ShortCut!"
}
]}>
<Input maxLength={3} />
</Form.Item>
< Form.Item
wrapperCol={{
span: 5,
offset: 7
}}
style={{ marginTop: "35px" }}
>
<Button
type="primary"
htmlType="submit"
size="large"
loading={editLoading}
>
Update Type
</Button>
</Form.Item>
</Form>
</Modal>
}
{
productTypeList !== undefined &&
<div style={{ marginLeft: "30px", marginRight: "30px" }}>
<Table
className="product-type-list-table"
columns={columns}
pagination={false}
dataSource={productTypeList}
rowKey={record => record.id}
/>
</div>
}
</div >
)
}
I don't know if it's the solution, but I'd suggest not messing up the rendering of the modal with both conditional rendering and a visible prop, it makes it hard to track.
Try something like
visible={selectedType !== undefined && showEditModal}
Or better yet, just get rid of the showEditModal variable since you're always changing it in accord with selectedType.
I think you can do it by changing you close function to
const handleEditClose = () => {
setshowEditModal(null);
};
Set the initial value of selectedType to null
and your modal's props to
<Modal
title="Edit Type"
visible={!!selectedType}
(Though it might give you an error because it might try to render the modal when selectedType is null, then just switch back to conditional rendering and set the visible prop to be always true)
import {
Button, Form,
Input,Table,
Modal
} from "antd";
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
const ManageTypeScreen = () => {
const dispatch = useDispatch();
const productTypeList = useSelector(state => state.productTypeList);
const [editForm] = Form.useForm();
const [selectedType, setselectedType] = useState({});
const [showEditModal, setshowEditModal] = useState(false);
useEffect(() => {
getProductTypeList(dispatch);
}, [])
const handleEditClose = () => {
setshowEditModal(false);
};
const handleEditShow = (val) => {
console.log(val);
setselectedType(val);
setshowEditModal(true);
};
const columns = [
{
title: 'Type Name',
dataIndex: 'name',
key: 'name'
},
{
title: 'Short Cut',
dataIndex: 'shortCut',
key: 'shortCut'
},
{
title: 'Edit',
sortable: false,
filterable: false,
render: (text, pro) => (
<div>
<Button type="primary" size="sm block" onClick={() => handleEditShow(pro)}>
Edit
</Button>
</div>)
},
];
return (
{selectedType !== undefined &&
<Modal
title="Edit Type"
visible={showEditModal}
onCancel={handleEditClose}
destroyOnClose={true}
footer={null}
centered={true}
>
<Form
labelCol={{ span: 9 }}
wrapperCol={{ span: 12 }}
layout="horizontal"
form={editForm}
requiredMark={true}
size="medium"
>
<Form.Item
label="Type Name:"
name="name"
initialValue={selectedType.name}
preserve={false}
rules={[
{ required: true, message: "Please input Name!" }
]}>
<Input maxLength={32} />
</Form.Item>
<Form.Item
label="ShortCut"
name="shortCut"
initialValue={selectedType.shortCut}
preserve={false}
rules={[
{
required: true,
message: "Please input ShortCut!"
}
]}>
<Input maxLength={3} />
</Form.Item>
< Form.Item
wrapperCol={{
span: 5,
offset: 7
}}
style={{ marginTop: "35px" }}
>
<Button
type="primary"
htmlType="submit"
size="large"
loading={editLoading}
>
Update Type
</Button>
</Form.Item>
</Form>
</Modal>
}
{
productTypeList !== undefined &&
<div style={{ marginLeft: "30px", marginRight: "30px" }}>
<Table
className="product-type-list-table"
columns={columns}
pagination={false}
dataSource={productTypeList}
rowKey={record => record.id}
/>
</div>
}
</div >
)
}
I'm using Radio.Group in Ant Design form and depending on the option I would like to display a custom message below. I'm trying to use form.getFieldValue() and my code looks like this, but it doesn't work. How can I fix it?
const options = [
{
value: 1,
label: "Option 1"
},
{
value: 2,
label: "Option 2"
}
];
const Demo = () => {
const [form] = Form.useForm();
const { getFieldValue } = form;
return (
<Form
form={form}
initialValues={{
radio: 1
}}
>
<Form.Item
label="Radio Group"
name="radio"
>
<Radio.Group options={options} />
</Form.Item>
{getFieldValue("radio") === 1 && <div>This is option 1</div>}
{getFieldValue("radio") === 2 && <div>This is option 2</div>}
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
Here is my codesandbox.
I think it's will help you to understand
const Demo = () => {
const [form] = Form.useForm();
const { getFieldValue, validateFields } = form;
const [radioValue, setRadioValue] = useState(1);
const onValidateForm = async () => {
const value = await validateFields();
window.alert(JSON.stringify(value, 2));
};
return (
<Form
{...layout}
form={form}
name="basic"
initialValues={{
radio: 1
}}
>
<Form.Item label="Options" name="radio">
<Radio.Group options={options} onChange={e => setRadioValue(e.target.value)} />
</Form.Item>
{radioValue === 1 && <div>This is option 1</div>}
{radioValue === 2 && <div>This is option 2</div>}
<Form.Item>
<Button type="primary" htmlType="submit" onClick={onValidateForm}>
Submit
</Button>
</Form.Item>
</Form>
);
};
I am making a different form component using ant design and trying to bind several different inputs
Here is the code :
this.state = {
title: '',
product: '',
options: 0,
price: 0,
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onProductChange = (e) => {
this.setState({
product: e.target.value
})
}
onHandleChange = value => {
this.setState({
priceOption: value
})
}
onNumberChange = e => {
this.setState({
price: e.target.value
})
}
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'title'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('title', {
rules: [
{ required: true, message: 'title is required' },
],
})(<Input onChange={this.onTitleChange}/>)}
</FormItem>
</Col>
</FormItemRow>
<FormItemRow>
<Col span={24} style={colStyle}>
<FormItem label={'product-number'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('product-number', {
rules: [{ required: true, message: 'product-number is required' }],
})(<Input onChange={this.onProductChange}/>)}
</FormItem>
</Col>
</FormItemRow>
<FormItemRow>
<Col span={12} style={colStyle}>
<FormItem label={'options'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('options', {
rules: [
{ required: true, message: 'options is required' },
],
})(<Select onChange={this.onHandleChange}>{this.handWashOptions(this.props.handWashOptions)}</Select>)}
</FormItem>
</Col>
<Col span={12} style={colStyle}>
<FormItem label={'price'} colon={false} style={{ marginBottom: 0 }}>
{getFieldDecorator('price', {
rules: [
{ required: true, message: 'price is required' },
],
})(<Input type="number" onChange={this.onNumberChange}/>)}
</FormItem>
</Col>
</FormItemRow>
title and product using just Input component.
option is using Select component.
and price is using Input type number component.
I think it is very inefficient using different onChange callback on each input component.
Is there any way I can bind one onChange callback function?
You can simply make a generic function handleChange, pass the name and value which are to be updated
handleChange(name,value){
this.setState({[name]: value})
}
and pass value to handle change like this
<Input onChange={(name,value)=>this.onTitleChange(name,value)}/>
You can use e.target.value to get value from target, also if you need to have different logic for some particular element then you can simply add a exception in handleChange
this.state = {
fields:{
each:
field:
you:
have:
}
}
handleChange = (event) => {
const { value, type, name } = event.currentTarget
console.log(type, value, name);
this.setState(prevState => ({
fields: {
...prevState.fields,
[name]: type === 'number' ? parseInt(value, 10) : value
}
}));
};
As stated in Handling Multiple Inputs,
add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name.
So you can have:
onChange = event => {
const name = event.target.name;
this.setState({
[name]: event.target.value
})
}
// Add `name` attribute used for event.target.name
<Select onChange={this.onChange} name="wash">...</Select>
<Input type="number" onChange={this.onChange} name="number"/>
<Input onChange={this.onChange} name="product" />
I have a dynamic array of values that i want to be able to change as I type and submit textfields. How do i properly change the value of the variable in the correct object of the array by submitting the textfields?
I have tried relating the key of the exercise to the values i need but its not working.
this is the parent function
export default class AddWorkoutForm extends Component {
state = {
step: 1,
name: '',
duration: 0,
date: '',
exerciselist: [
{
id: uuid.v4(),
exerciseName: '',
numberOfSets: 0,
weight: 0,
reps: 0
}
],
}
// Generate Exercise objects in exerciselist
addExercises = () => {
this.setState({
exerciselist: [
...this.state.exerciselist,
{
id: uuid.v4(),
exerciseName: '',
numberOfSets: 0,
weight: 0,
reps: 0
}
]
})
}
// Remove exercise object in exerciselist
removeExercises = (id) => {
if (this.state.exerciselist.length > 1) {
this.setState({
exerciselist: [...this.state.exerciselist.filter(exercise => exercise.id !== id)]
})
}
}
// Proceed to next step
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
};
// Go back to prev step
prevStep = () => {
const { step } = this.state;
this.setState({
step: step - 1
});
};
// Handle fields change
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
const { step } = this.state;
const { name, duration, date, exerciselist } = this.state;
const values = { name, duration, date, exerciselist };
switch (step) {
case 1:
return (
<AddWorkout
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
);
case 2:
return (
<AddExercise
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange={this.handleChange}
addExercises={this.addExercises}
removeExercises={this.removeExercises}
values={values}
/>
);
case 3:
return (
<AddWorkoutConfirm
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange={this.handleChange}
values={values}
/>
);
}
}
}
This is the code for mapping the fields for each object in the dynamic array:
export default class AddExercise extends Component {
continue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
this.props.prevStep();
};
render() {
const { values, handleChange, addExercises, removeExercises } = this.props;
const { exerciselist } = values;
return (
<div style={{ textAlign: "center", height: "100%" }}>
<ClientMenuBar title="Add An Exercise" />
<h2>Enter Your Exercise Details</h2>
<form>
<div style={styles.form}>
{exerciselist.map((exercise) => {
return (
<div style={styles.textfieldWrapper} key={exercise.id}>
<TextField
fullWidth
label="Exercise Name"
margin="dense"
name="exerciseName"
onChange={handleChange}
defaultValue={exercise.exerciseName}
/>
<TextField
label="Starting # Of Reps"
margin="dense"
type="number"
style={styles.textfield}
name="reps"
onChange={handleChange}
defaultValue={exercise.reps}
/>
<TextField
label="Starting Weight"
margin="dense"
type="number"
style={styles.textfield}
name="weight"
onChange={handleChange}
defaultValue={exercise.weight}
/>
<TextField
label="# of Sets"
margin="dense"
type="number"
style={styles.textfield}
name="numberOfSets"
onChange={handleChange}
defaultValue={exercise.numberOfSets}
/>
<Button
onClick={() => removeExercises(exercise.id)}
size="small"
disableRipple
fullWidth
>
REMOVE EXERCISE
</Button>
</div>
);
})}
<Button
onClick={addExercises}
size="small"
disableRipple
fullWidth
>
ADD EXERCISE
</Button>
</div>
<div style={styles.buttonWrapper}>
<Button
color="inherit"
variant="contained"
style={styles.button}
size="large"
onClick={this.back}
>
back
</Button>
<Button
color="primary"
variant="contained"
style={styles.button}
size="large"
onClick={this.continue}
>
Next
</Button>
</div>
</form>
</div>
)
}
}
And this is where i am printing out what i have:
export default class AddWorkoutConfirm extends Component {
continue = e => {
e.preventDefault();
// PROCESS FORM //
this.props.nextStep();
};
back = e => {
e.preventDefault();
this.props.prevStep();
};
render() {
const { name, duration, date, exerciselist } = this.props.values;
return (
<div style={{ textAlign: "center", height: "100%" }}>
<ClientMenuBar title="Confirm Your Details" />
<h2>Enter Your Workout Details</h2>
<form style={styles.form}>
<List>
<ListItem>Workout Name: {name}</ListItem>
<ListItem>Estimated Duration Of Workout: {duration} mins</ListItem>
<ListItem>Number Of Exercises: {exerciselist.length}</ListItem>
<ListItem>Date Of Workout: {date}</ListItem>
</List>
<div style={{borderTop:"1px solid gray"}}>
{
exerciselist.map((exercise) => {
return (
<List key={exercise.id}>
<ListItem>Exercise Name: {exercise.exerciseName}</ListItem>
<ListItem>Number of Sets: {exercise.numberOfSets}</ListItem>
<ListItem>Starting Weight: {exercise.weight} lbs</ListItem>
<ListItem>Starting Number Of Reps: {exercise.reps}</ListItem>
</List>
)
})}
</div>
<br />
<div style={styles.buttonWrapper}>
<Button
color="inherit"
variant="contained"
style={styles.button}
size="large"
onClick={this.back}
>
back
</Button>
<Button
color="primary"
variant="contained"
style={styles.button}
size="large"
onClick={this.continue}
>
Confirm
</Button>
</div>
</form>
</div>
)
}
}
It properly prints out the values that are not in an array but i dont know how to use a handleChange for the values in the array. It's not recording what i am typing at all for the dynamic arrays values.
As far as I see handleChange is triggered when there is change event on the TextField. Each TextField component represents an attribute of exercise in the exerciselist that is in your state. Now when user tries to add an exercise, you call the handleChange which sets in the state the value for exerciseName and same for other attributes. But when user clicks on a Button that adds the exercise, you are not constructing a new exercise object and adding it to your exerciselist. Instead your function addExercises below adds a default exercise object which has an auto generated uuid, and empty exerciseName, 0 numberOfSets, weight and reps. You need to pass the exerciseName, numberOfSets, weight, reps to this function that user entered on the form -> construct a new exercise object and then add that object to your exerciseList array in the state.
`
addExercises = () => {
this.setState({
exerciselist: [
...this.state.exerciselist,
{
id: uuid.v4(),
exerciseName: '',
numberOfSets: 0,
weight: 0,
reps: 0
}
]
})
}`
Hope that helps!