Ant Design cannot update initial Value for dynamic Form Item - reactjs

I am using Ant Design 4 to make a dynamic form like this: there is an "Add form item" button, when you click it, you can add a select box to your form. After selecting one option from that select box, there will be some inputs with an initial value based on the selected value data. My problem is these inputs fields only render one time when selecting one value from the select box for the first time. I have logged the "item" data inside infoData[0]?.map((item, index), it returns with the right value but the Form Inputs do not update their initialValue. Here is my code:
import React, { useState, useCallback } from "react";
import { Button, Form, Select, Input } from "antd";
const FormPage = () => {
const [userData, setUserData] = useState([]);
const [optionList, setOptionList] = useState([
{
name: "Option 1",
value: "o-1",
info: [
{ name: "Option 1 Info 1", point: 11 },
{ name: "Option 1 Info 2", point: 12 },
],
},
{
name: "Option 2",
value: "o-2",
info: [
{ name: "Option 2 Info 1", point: 13 },
{ name: "Option 2 Info 2", point: 14 },
],
},
]);
const onSubmitForm = (values: object) => {
console.log(values);
};
const addUserData = () => {
const newData = {
id: Math.random().toString(36).substring(7),
userName: Math.random().toString(36).substring(7),
};
setUserData([...userData, newData]);
};
const selectedOption = (val) => {
const selectedData = optionList.filter((item) => item.value === val);
return selectedData[0].info;
};
return (
<Form onFinish={onSubmitForm}>
<p>
<Button onClick={addUserData}>Add Form Item</Button>
</p>
{userData.length > 0
? userData.map((item, index) => (
<FormPageItem
key={index}
data={item}
index={index}
addUserData={addUserData}
optionList={optionList}
selectedOption={selectedOption}
/>
))
: ""}
<Button type="primary">Submit</Button>
</Form>
);
};
const FormPageItem = (props) => {
const [infoData, setInfoData] = useState([]);
const handleSelectOption = (value) => {
const selected = props.selectedOption(value);
console.log("selected", selected);
const newData = [...infoData];
newData[0] = selected;
console.log(newData);
setInfoData(newData);
};
const renderInput = useCallback(() => {
if (infoData.length > 0) {
return (
<>
{infoData[0]?.map((item, index) => (
<Form.Item
name={[`option-${props.data.id}`, `user-info-${index}`]}
style={{ marginBottom: 16 }}
initialValue={item.name}
key={index}
>
<Input />
</Form.Item>
))}
</>
);
}
return "";
}, [infoData]);
return (
<div>
<Form.Item
name={[`option-${props.data.id}`, "options"]}
label="Option List"
>
<Select showArrow={false} onChange={handleSelectOption}>
{props.optionList.map((item) => (
<Select.Option value={item.value} key={item.value}>
{item.name}
</Select.Option>
))}
</Select>
{renderInput()}
</Form.Item>
</div>
);
};
export default FormPage;

Please see the documents here ant design form documents:
Note that initialValues cannot be updated by setState dynamically, you should use setFieldsValue in that situation.
In your case, you can just replace renderInput function with simple render function, and even no need of onChange function for the first Select component.
const FormPageItem = (props) => {
const [infoData, setInfoData] = useState([]);
return (
<div>
<Form.Item
name={[`option-${props.data.id}`, "options"]}
label="Option List"
>
<Select showArrow={false} >
{props.optionList.map((item) => (
<Select.Option value={item.value} key={item.value}>
{item.name}
</Select.Option>
))}
</Select>
{/* {renderInput()} */}
</Form.Item>
<Form.Item
dependencies={[[`option-${props.data.id}`,"options"]]}
>
{({getFieldValue})=>{
const v = getFieldValue([`option-${props.data.id}`,"options"])
if(!v){return null}
const selected = props.selectedOption(v);
return (
<>
{selected?.map((item, index) => (
<Form.Item
name={[`option-${props.data.id}`, `user-info-${index}`]}
style={{ marginBottom: 16 }}
initialValue={item.name}
key={index}
>
<Input />
</Form.Item>
))}
</>
)
}}
</Form.Item>
</div>
);
};

Related

How to get the values of multiple input

I have a list of products with different values.
const products = [{id: 2, value: 'A'}, {id:3, value: '3'}] // sample input
const RenderProduct = products.map((product) => {
return(
<div key={product.id}>
<MinusIcon onClick={SHOULD_DEDUCT_THE_QUANTITY}/>
<input type="text" value={SHOULD_DISPLAY_THE_QUANTITY_HERE} />
<AddIcon onClick={SHOULD_ADD_THE_QUANTITY}/>
</div>
)
});
return <RenderProduct />
How can I retrieve the current quantity of each product and display it on inputbox?
You can create a state using useState and based on the operation and id you can get the updated value
CODESANDBOX LINK
function Button({ onClick, operation }: any) {
return <button onClick={onClick}> {operation} </button>;
}
export default function App() {
const [products, setProducts] = useState([
{ id: 2, value: 2 },
{ id: 3, value: 3 }
]);
function handleChange(id: number, operation: "minus" | "add") {
setProducts((p) => {
return p.map((product) => {
if (product.id !== id) return product;
return {
...product,
value: operation === "minus" ? product.value - 1 : product.value + 1
};
});
});
}
return (
<>
{products.map((product) => {
return (
<div key={product.id}>
<Button
operation="-"
onClick={() => handleChange(product.id, "minus")}
/>
<input type="text" value={product.value} />
<Button
operation="+"
onClick={() => handleChange(product.id, "add")}
/>
</div>
);
})}
</>
);
}

Using Formik on IndexTable with editable fields

I am trying to come up with a list that has an editable Quantity field. Here's what I have now.
As you can see, I'm unable to map the Quantity field. The values there comes from Shopify's ResourcePicker. It does not have a quantity so I manually update the object to set a default quantity of 1. This quantity should be editable as seen in the screenshot above.
Here's the code that I currently have.
import { Button, Icon, IndexTable, TextField } from '#shopify/polaris'
import { Form, Formik } from 'formik'
import { MobilePlusMajor } from '#shopify/polaris-icons'
import { ResourcePicker } from '#shopify/app-bridge-react'
import { useEffect, useState } from 'react'
function SelectProducts({ form, dispatch }) {
const [isProductPickerOpen, setIsProductPickerOpen] = useState(false)
const [showProducts, setShowProducts] = useState(false)
const [chosenProducts, setChosenProducts] = useState([])
const productPickerOnSelectHandler = (selectPayload) => {
setIsProductPickerOpen(false)
// Add quantity to selection
const updatedSelection = selectPayload.selection.map((product) => {
const variants = product.variants.map((variant) => ({
...variant,
quantity: 1,
}))
return {
...product,
variants,
}
})
setChosenProducts(updatedSelection)
}
useEffect(() => {
if (!chosenProducts) return
dispatch({
type: 'SET_PRODUCT_SELECTION_DATA_ON_CREATE_SUBSCRIPTION',
payload: chosenProducts,
})
}, [chosenProducts, dispatch])
const productPickerOnCancelHandler = () => {
setIsProductPickerOpen(false)
}
useEffect(() => {
if (
form.productSelectionData &&
Object.keys(form.productSelectionData).length > 0
) {
setShowProducts(true)
} else {
setShowProducts(false)
}
}, [form])
return (
<>
{showProducts ? (
<Formik initialValues={form.productSelectionData}>
{({ setFieldValue, values, errors, isSubmitting }) => (
<Form>
<IndexTable
resourceName={{
singular: 'product',
plural: 'products',
}}
onSelectionChange={() => {
console.log('selection changed.')
}}
headings={[
{ title: 'Product' },
{ title: 'Quantity' },
{ title: 'Price' },
{ title: 'Subtotal' },
]}
emptyState={null}
itemCount={1}
>
{values.map((product, pIndex) =>
product.variants?.map((variant, vIndex) => (
<IndexTable.Row
id={variant.id}
key={variant.id}
position={vIndex}
>
<IndexTable.Cell fontWeight="bold">
<div onClick={(e) => e.stopPropagation()}>
{variant.displayName}
</div>
</IndexTable.Cell>
<IndexTable.Cell>
<TextField
type="number"
value={variant.quantity}
onChange={(value) => {
console.log('got in here')
setFieldValue(
`products[${pIndex}].variants[${vIndex}].quantity`,
value
)
}}
/>
</IndexTable.Cell>
<IndexTable.Cell>
<div onClick={(e) => e.stopPropagation()}>
{variant.price}
</div>
</IndexTable.Cell>
<IndexTable.Cell>
<div onClick={(e) => e.stopPropagation()}>
{Number(variant.quantity * variant.price)}
</div>
</IndexTable.Cell>
</IndexTable.Row>
))
)}
</IndexTable>
</Form>
)}
</Formik>
) : null}
<Button
icon={<Icon source={MobilePlusMajor} />}
onClick={() => {
setIsProductPickerOpen(true)
}}
>
Add products and variants
</Button>
<ResourcePicker
resourceType="Product"
open={isProductPickerOpen}
showVariants
onSelection={productPickerOnSelectHandler}
onCancel={productPickerOnCancelHandler}
/>
<p> </p>
</>
)
}
export default SelectProducts
I am not sure what to put in the setFieldValue for variant.quantity. How do I set it correctly? Or am I understanding this the wrong way?

react.js react-select onselected value change another select value and submit multiple items to array

I have stomped on a problem that i don't know how to resolve.
I have a react-select input that passes a selected value to another select input.
When a user clicks on submit button then i have to display an array of every selector input that the user has selected as a list of items and then the form should reset all select values.
For this, i have tried submitting to an array but it only shows one item from all selectors and form doesn't reset its values.
Here is a sandbox link
https://codesandbox.io/s/awesome-carson-i99g8p?file=/src/App.js
How can I archive this I have tried everything but I could not figure out how can i archive this functionality.
Ok. First tricky thing of react-select is, the value you assign to the component must be an object with label and valueproperties, not just the value. Meaning, when handling change events, you should set the state using the full event object.
This is the code mostly fixed:
import React, { useState, useEffect } from "react";
import Select from "react-select";
const options = [
{ value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" }
];
const options2 = [
{ value: "Before Due Date", label: "Before Due Date" },
{ value: "After Due Date", label: "After Due Date" }
];
const App = (props) => {
const [numOfDays, setNumOfDays] = useState('');
const [beforeDueDate, setBeforeDueDate] = useState('');
const [items, setItems] = useState([]);
const [reminders, setReminders] = useState([]);
const submit = (event) => {
event.preventDefault(); // <-- prevent form submit action
const obj = {};
obj.id = reminders.length + 1;
obj.numOfDays = numOfDays.value;
obj.beforeDueDate = beforeDueDate.value;
setReminders((items) => [...items, obj]); // <-- update arr state
setBeforeDueDate("");
setNumOfDays("");
};
function numOfDaysHandle(event) {
// const numOfDays = event.value;
setNumOfDays(event);
setItems((items) => [...items, items]);
}
function beforeDueDateHandle(event) {
// const value = event.value;
setBeforeDueDate(event);
}
const removeReminder = (id) => {
setReminders(reminders.filter((item) => item.id !== id));
};
return (
<>
<form>
<div>
{reminders.map((item, index) => (
<div key={index}>
<div>
<span>
{item.numOfDays} days {item.beforeDueDate}
</span>
<button onClick={() => removeReminder(item.id)}>
removeItem
</button>
</div>
</div>
))}
</div>
<div>
<Select
options={options}
value={numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
//onChange={numOfDaysHandle}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
/>
</div>
{items.map((item, index) => (
<div key={index}>
<Select
options={options}
value={item.numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={item.beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
// onClick={() => setItems((items) => [...items, items])}
/>
</div>
))}
<button
onClick={submit}
//disabled={!numOfDays}
>
Set Reminder
</button>
</form>
</>
);
};
export default App;
Please see if you can move forward now, I could not understand what you want exactly with the array of <Select /> elements.

How to set default value to selected in material-UI multi select box in reactjs?

I am using Multi Select box from material-ui v4.
I set a default value in the useState and I can see the values but when I open the select the default list is not selected and when I click on it, it added again to the select box so I have the same value multiple time.
/*
the full list configurationList [{_id: '21375d87eed65e103c790121', configurationName: 'Regime1'},{_id: '21375d87eed65e103c790122', configurationName: 'Regime2'},
{_id: '21375d87eed65e103c790123', configurationName: 'Regime3'}]
The currentHistogramConfigurationList [{_id: '21375d87eed65e103c790121', configurationName: 'Regime1'}]*/
const [
histogrammeConfigurations,
setHistogrammeConfigurations
] = useStateWithDep(
currentHistogramConfigurationList ? currentHistogramConfigurationList : []
);
const handleChange = (event) => {
console.log('*handle_Change', event.target.value);
setHistogrammeConfigurations(event.target.value);
};
const handleDelete = (_id) => () => {
console.log('*handle_Delete', _id);
const chipDataConst = [...histogrammeConfigurations];
const chipToDelete = chipDataConst.map(({ _id }) => _id).indexOf(_id);
console.log('*handle_chipToDelete', chipToDelete);
chipDataConst.splice(chipToDelete, 1);
setHistogrammeConfigurations(chipDataConst);
};
//...
<Select
labelId='demo-mutiple-chip-label'
id='demo-mutiple-chip'
multiple
value={histogrammeConfigurations}
onChange={handleChange}
input={<Input id='select-multiple-chip' />}
renderValue={(selected) => (
<div className={classes.chips}>
{selected.map((value) => {
return (
<Chip
key={value._id}
label={value.configurationName}
className={classes.chip}
onDelete={handleDelete(value._id)}
/>
);
})}
</div>
)}
MenuProps={MenuProps}
>
{configurationList.map((item) => (
<MenuItem
key={item._id}
value={item}
style={getStyles(item._id, histogrammeConfigurations, theme)}
// selected
>
{item.configurationName}
</MenuItem>
))}
</Select>
Try to handle the input like this
const [histogrammeConfigurations, setHistogrammeConfigurations ] = useStateWithDep(currentHistogramConfigurationList ? currentHistogramConfigurationList : []);
<Select
value={histogrammeConfigurations}
input={
<Input
id='select-multiple-chip'
value={histogrammeConfigurations}
/>
}
>
...
</Select>

Radio buttons for redux form field

It doesn't work correctly, I want to select only one value, but I can select one and more... and can't deselect
there is code for that function.
const RadioButtonField = ({type, options, disabled, onChange}) => {
const handleChange = (value) => {
onChange(type, value);
};
return (
<div>
{options.map(({ value, label }) =>
<Radio.Group key={value}>
<Radio
disabled={disabled}
onChange={() => handleChange(value)}
>
{label}
</Radio>
</Radio.Group>
)}
</div>
);
};
You can try this for single selection and also you can reselect too
import React from "react";
import "./styles.css";
import Radio from "#material-ui/core/Radio";
export default class App extends React.Component {
state = {
selectedValue: null,
radioOptions: [
{ id: 1, title: "1" },
{ id: 2, title: "2" },
{ id: 3, title: "3" },
{ id: 4, title: "4" }
]
};
handleChange = id => {
this.setState({
selectedValue: id
});
};
render() {
const { selectedValue, radioOptions } = this.state;
return (
<div className="App">
{radioOptions.map(option => {
return (
<div className="radio-parent">
<lable
onClick={() => this.handleChange(option.id)}
className="radio-btn"
>
<Radio
color="default"
value={option.id}
name="radioValue"
checked={selectedValue == option.id}
/>
{option.title}
</lable>
</div>
);
})}
</div>
);
}
}
codesandBox link for Demo
You can refer to the following code:
class App extends React.Component {
state = {
initialValue: "A",
options: ["A", "B", "C", "D"]
};
onChange = e => {
console.log("radio checked", e.target.value);
this.setState({
value: e.target.value
});
};
render() {
return (
<Radio.Group value={this.state.initialValue}>
{this.state.options.map((value, index) => {
return (
<Radio onChange={this.onChange} key={index} value={value}>
{" "}
{value}{" "}
</Radio>
);
})}
</Radio.Group>
);
}
}
Working codesandbox demo
I think you do not need to pass anything to the function. Whenever the option will be clicked, the event (e) object will be passed to onChange(e) and you will get the value of clicked item and checked value in onChange(e) e.target.value and e.target.checked respectively.

Resources