I am using formik and fieldarray for creating dynamic form and I am using react-select. I am working on edit of items. On Edit if there exists previous values then those values must be shown and those values can be changed, but somehow I am unable to create. Here's my code.
here is the code for initial value of formik
const initialOrderItemData = orderItems && orderItems.map((orderItem) => {
return {
itemId: orderItem.itemId,
quantity: orderItem.quantity,
}
});
const initialOrderInfo = {
orderedItemDataArray: initialOrderItemData,
};
This is my form
<Formik
initialValues={initialOrderInfo}
onSubmit={handleSubmit}
validationSchema={HotelOrderSchema}
>
{({ errors, setFieldValue, touched, values, name, }) => (
<Form>
<Row>
<Table size="sm" hover>
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<FieldArray
name="orderedItemDataArray"
render={({ remove, push }) => (
<>
{values.orderedItemDataArray.length > 0 &&
values.orderedItemDataArray.map(
(order, index) => (
<tr key={index}>
<td>
<Select
as="select"
name={`orderedItemDataArray.${index}.itemId`}
options={menuOptions}
placeholder="Type"
// value={selectedItems(order.itemId)}
value={menuOptions.find(op => {
return op.value === order.itemId
})}
/>
</td>
<td>
<Field
type="number"
className="form-control"
name={`orderedItemDataArray.${index}.quantity`}
min="0"
placeholder="Quantity"
/>
</td>
<td>
<ButtonGroup>
{values.orderedItemDataArray
.length !== 1 && (
<Button
color="primary"
size="sm"
onClick={() => remove(index)}
>
-
</Button>
)}
{values.orderedItemDataArray
.length -
1 ===
index && (
<Button
color="secondary"
size="sm"
onClick={() =>
push({
itemId: '',
quantity: '',
})
}
>
+
</Button>
)}
</ButtonGroup>
</td>
</tr>
)
)}
</>
)}
/>
</tbody>
</Table>
</Row>
</Form>
</Formik>
with this code I am able to get the value but I am unable to change the value and set the new value.
In your select component you must include an onChange event like so
//set a state for the changed selected option
const [selectedOption,setSelectedOption] = useState()
<Select
as="select"
name={`orderedItemDataArray.${index}.itemId`}
options={menuOptions}
placeholder="Type"
// value={selectedItems(order.itemId)}
value={menuOptions.find(op => {
return op.value === order.itemId
onChange={(e) => {
setSelectedOption(e.target.value)
}}
})}
/>
//then if you wanna access the selected option you can use the useState value.
<p>{selectedOption}</p>
Related
My problem is not able render the html code in the editor.
Here is my code for your reference :
const divRef = useRef(null);
const [contentState, setContentState] = useState(); // ContentState JSON
const blocksFromHTML = convertFromHTML(divRef.current.innerHTML);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.state = {
editorState: EditorState.createWithContent(state)
}
I want to render the table data on the editor. Here is my html code for your reference.
<div className='row' ref={divRef}>
<div className='col-lg-12'>
<table id="checklist">
<tr>
<th>Task List</th>
<th>Assignee</th>
<th>Status</th>
<th>Comments</th>
</tr>
{
ViewChecklistItem.map((row, index) => (
<tr key={row.TaskId}>
<td width="45%">{row.TaskName}</td>
<td>{row.AssigneeName}</td>
<td>
<FormControl sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="demo-controlled-open-select-label">Status</InputLabel>
<Select
className={"copy "+(row.AllowStatusChange === 'Y' ? 'allowed' : 'not-allowed')}
labelId="demo-controlled-open-select-label"
id="demo-controlled-open-select"
open={open[index]}
onClose={() => { handleClose(index) }}
onOpen={() => { handleOpen(index) }}
value={status[index] || row.StatusId}
label="Status"
onChange={(event, newValue) => {
setStatusId(event.target.value);
setTaskId(row.TaskId);
setStatusPopup(true);
}}
>
{StatusList?.map(status => {
return (
<MenuItem value={status.StatusId}>{status.Status}</MenuItem>
);
})}
</Select>
</FormControl>
</td>
<td><a href="javascript:;" onClick={() => viewLogs(row.TaskId, row.TaskName, row.AssigneeName)} className='view-log'><img src={view_log} /> View Log</a></td>
</tr>
))
}
</table>
</div>
</div>
But am getting the output in console.log(divRef.current.innerHTML);
Please help me out. where I missed.
I have upated Editior code below:
<Editor
editorState={editorState}
defaultContentState={contentState}
onContentStateChange={setContentState}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
How can I use useDisclouse() in Chakra with a button in a forEach loop. When I clicked on onClick event a Button it trigger all Buttons.
const {isOpen:isOpenEdit,onOpen:onOpenEdit,onClose:onCloseEdit} = useDisclosure();
...
return (<Tbody>
{!isLoading &&
data.data?.map((row,i) => (
<Tr ref={rows.current[i]=createRef()} key={i}>
<Td>{`$${row.price}`}</Td>
<Td maxW={"10em"} overflow={"hidden"}>
{row.title}
</Td>
<Td maxW={"10em"} overflow={"hidden"}>
{row.description}
</Td>
<Td>{row.brand}</Td>
<Td maxW={"10em"} overflow={"hidden"}>{Date(row.date).toString()}</Td>
<Td>{row.location}</Td>
<Td>
{row.photo}
</Td>
<Td>{row.phones?.toString()}</Td>
<Td>
<ButtonGroup isAttached>
<Button key={row.id} onClick={()=>onOpenEdit()} >Edit</Button>
<EditModal key={i} isOpen={isOpenEdit} onClose={onCloseEdit} id={row.id}/>
</ButtonGroup>
</Td>
</Tr>
))}
</Tbody>)
useDisclosure only allows you to keep track of one boolean value. So for this we need to build something ourselfs.
First we need a state to keep track of the opened modals.
const [isOpenEdits, setIsOpenEdits] = useState({});
Now we need a function which helps us to set the state based on the id of the row.
const toggleIsOpen = (id, isOpen) => {
setIsOpenEdits((prevState) => {
return {
...prevState,
[id]: isOpen,
};
});
};
We can use this toggleIsOpen function in the ButtonGroup component and set the isOpen prop to the value of isOpenEdits[row.id] defaulting to false.
<ButtonGroup isAttached>
<Button key={row.id} onClick={() => toggleIsOpen(row.id, true)}>
Edit
</Button>
<EditModal
key={i}
isOpen={isOpenEdits[row.id] ?? false}
onClose={() => toggleIsOpen(row.id, false)}
id={row.id}
/>
</ButtonGroup>
function EachData({ data, index, open }) {
const [isOpen, setisOpen] = useState(open);
const toggle = (e) => {
setisOpen((prev) => !prev);
};
return (
<tr>
{/*<td></td>'s ... */}
<td>
<div className="functions">
{!isOpen ? (
<>
<label className="far fa-edit" htmlFor={`label-${index + 1}`}>
<input type="radio" name="edit" id={`label-${index + 1}`} onChange={toggle} />
</label>
<label className="far fa-trash"></label>
</>
) : (
<>
<label className="far fa-circle-check"></label>
<label className="far fa-times-circle" htmlFor={`label-${index + 1}`} >
<input type="radio" name="edit" id={`label-${index + 1}`} onChange={toggle} />
</label>
</>
)}
</div>
</td>
</tr>
);
}
export default EachData;
App.js
array.map((data, index)=>{
return(
<EachData data={data} index={index} isOpen={false}/>
)
})
When I check the radio buttons the jsx changes as expected, but after checking another radio button the previous one's state remains true. How do I set those elements state to false ?
You should store isOpen state in your array data, not in EachData component
const [array, setArray] = useState();
const toggle = idx => {
const newArray = array.map((item, index) => {
if (idx == index) return {
...item,
isOpen: !item.isOpen
}
return item
})
setArray(newArray);
}
array.map((data, index) => {
return <EachData data={data} index={index} isOpen={data.isOpen} toggle={toggle} />;
});
function EachData({ data, index, isOpen, toggle }) {
return (
<tr>
{/*<td></td>'s ... */}
<td>
<div className="functions">
{!isOpen ? (
<>
<label className="far fa-edit" htmlFor={`label-${index + 1}`}>
<input
type="radio"
name="edit"
id={`label-${index + 1}`}
onChange={() => toggle(index)}
/>
</label>
<label className="far fa-trash"></label>
</>
) : (
<>
<label className="far fa-circle-check"></label>
<label className="far fa-times-circle" htmlFor={`label-${index + 1}`}>
<input
type="radio"
name="edit"
id={`label-${index + 1}`}
onChange={() => toggle(index)}
/>
</label>
</>
)}
</div>
</td>
</tr>
);
}
You should use useRef instead of id, here I make some logic with useRef I hope this would be helpful.
As per my understand, when you click on radio button in map function this will be activated and when you click another radio button previous radio button is still showing active, In this code I create two useRef as you see the below code, one is taking for all indexes and second for removing previous radio button active. I hope you understand this code, if you know DOM.
function EachData({ data, index, open }) {
const [isOpen, setisOpen] = useState(open);
const radioRef = useRef([]);
const previousRadioRef = useRef([]);
const toggle = (i) => {
setisOpen((prev) => !prev);
if (previousRadioRef.current && previousRadioRef.current[0] !== radioRef.current[i]) {
if (radioRef.current[i]) {
if (previousRadioRef.current.length) {
previousRadioRef.current[0].checked = false;
previousRadioRef.current = [];
}
radioRef.current[i].checked = true;
previousRadioRef.current.push(radioRef.current[i]);
}
} else if(previousRadioRef.current && previousRadioRef.current[0]) {
previousRadioRef.current[0].checked = false;
previousRadioRef.current = [];
}
};
return (
<>
<tr>
<td>
<div className="functions">
{!isOpen ? (
<>
<label className="far fa-edit">
<input type="radio" name="edit" ref={ref => (radioRef.current[index] = ref)} onChange={() => toggle(index)} />
</label>
<label className="far fa-trash"></label>
</>
) : (
<>
<label className="far fa-circle-check"></label>
<label className="far fa-times-circle">
<input type="radio" name="edit" ref={ref => (radioRef.current[index] = ref)} onChange={() => toggle(index)} />
</label>
</>
)}
</div>
</td>
</tr>
</>
);
}
export default EachData;
array.map((data, index)=>{
return(
<EachData data={data} index={index} open={false}/>
)
})
You can set state with the name of radio input.
Reference: http://react.tips/radio-buttons-in-reactjs/
I have a data table built in react hooks. On each row I'm allowing the user to edit the values and enter what I'm calling edit mode seen below:
enter const TableRow = (props: any) => {
const [inputs, setInputs] = useState({});
const [editMode, setEditMode] = useState(false)
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setInputs(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
}
const onStartEdit = () => setEditMode(true)
const onEditCommit = (event: MouseEvent<HTMLButtonElement>) => {
// console.log('on edit inputs: ', inputs)
// props.updateRow(inputs, props.id)
}
const onCancelEditMode = () => setEditMode(false)
return (
<tr>
<td>
<input
disabled={!editMode}
name="merchant"
placeholder="Merchant Name"
type="text"
onChange={handleChange}
defaultValue={props.row.merchant}
/>
</td>
<td>
<input
disabled={!editMode}
name="item"
placeholder="Item"
type="text"
onChange={handleChange}
defaultValue={props.row.item}
/>
</td>
<td>
{props.row.amtCrypto}
</td>
<td>
<input
disabled={!editMode}
name="currency"
placeholder="Currency"
type="text"
onChange={handleChange}
defaultValue={props.row.currency}
/>
</td>
<td>
{props.row.cryptoPrice}
</td>
<td>
<input
disabled={!editMode}
name="amount"
placeholder="Amount(USD)"
type="text"
onChange={handleChange}
defaultValue={props.row.amount}
/>
</td>
<td>
{!editMode &&
<div>
<button
onClick={onStartEdit}
>
Edit
</button>
<button
onClick={onDeleteRow}
>
Delete Row
</button>
</div>
}
{editMode &&
<div>
<button
onClick={onEditCommit}
>
Complete
</button>
<button
onClick={onCancelEditMode}
>
Cancel
</button>
</div>}
</td>
</tr>
)
}
So basically on startEditMode I am making the fields editable and on cancelEditMode I'd like to lock the fields and revert them back to the default values. How would I implement this?
First you can put the initial values received from props in the inputs state
const [inputs, setInputs] = useState({merchant: props.row.merchant, ...});
You should use the value of the inputs state as single source of truth.
<input
disabled={!editMode}
name="merchant"
placeholder="Merchant Name"
type="text"
onChange={handleChange}
value={inputs.merchant}
/>
When the onCancelEditMode function gets called you can set the input state with the initial values from props
const onCancelEditMode = () => {
setEditMode(false)
setInput({merchant: props.row.merchant, ...})
}
I am a professional web developer teaching myself react. I created this table as part of a larger form.
The table is invoked inside the form component
<ProductList
products={this.state.products}
onChange={products => this.sendUpdate('products', products)}
/>
this.sendUpdate:
sendUpdate(field, value) {
this.setState({[field]: value});
socket.emit('updateItem', this.state.id, {[field]: value});
}
That part is all working great with all my form updates. but now I am trying to figure out how to process the updates inside the table. Each product is a row of the table invoked like this:
<tbody>
{this.props.products.map((product, i) =>
<Product key={i} data={product} products={this}/>
)}
</tbody>
What is the proper way to update the state when I type in one of the inputs?
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
full code for ProductList
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
class Product extends React.Component {
updateField(...props){
this.props.products.updateProduct(this.data, ...props)
}
render() {
return (
<tr>
<td>
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
</td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl type="number"/></td>
<td><Button bsStyle="danger" onClick={() => this.props.products.deleteProduct(this.props.data)}>X</Button></td>
</tr>
);
}
}
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{timestamp: Date.now()}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
this.props.products;
// ???
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map(product => <Product key={product.timestamp} data={product} products={this}/>)}
</tbody>
</Table>
);
}
}
This is what I ended up with based on the accepted answer:
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
const products = this.props.products.map(p => {
return p === product ? {...p, [field]: newValue} : p;
});
this.props.onChange(products);
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive striped>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map((product, i) => this.renderRow(i, product, this))}
</tbody>
</Table>
);
}
renderRow(i, product) {
return (
<tr key={i}>
<td>
<FormControl
value={product.species || ''}
onChange={e => this.updateProduct(product, 'species', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dried || ''}
onChange={e => this.updateProduct(product, 'dried', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.cut || ''}
onChange={e => this.updateProduct(product, 'cut', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsGreen || ''}
onChange={e => this.updateProduct(product, 'dimensionsGreen', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsDry || ''}
onChange={e => this.updateProduct(product, 'dimensionsDry', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.color || ''}
onChange={e => this.updateProduct(product, 'color', e.target.value)}
/>
</td>
<td>
<FormControl
type="number"
value={product.quantity || 0}
onChange={e => this.updateProduct(product, 'quantity', e.target.value)}
/>
</td>
<td><Button bsStyle="danger" onClick={() => this.deleteProduct(product)}>X</Button></td>
</tr>
);
}
}
In your ProductsList's render(), change the array map to something like:
{this.props.products.map((product, index) => <Product key={product.timestamp} data={product} index={index} products={this}/>)}
Then in your Product's change the updateField() to:
updateField(...props){
this.props.products.updateProduct(this.props.index, ...props)
}
And finally, change ProductsList's updateProduct() to:
updateProduct(index, field, newValue) {
const products = this.props.products.map((product, productIndex)) => {
if (index === productIndex) {
return {
...product,
[field]: newValue
};
}
return product;
})
this.props.onChange(products);
}
Also, there's a slight typo in Product render. The FormControl's onClick should read onClick={e => this.updateField('species', e.target.value)}.