I have text fields that are being added dynamically on button click and mapping the items depending on state variable [inputList]. The problem is when I'm trying to delete a field using the splice or filter option it is always deleting the last item but in the console it is showing that it has deleted the specific item that I wanted but in UI it's removing the last field no matter which field I delete. For example, if there are 3 fields named 'A', 'B'& 'C'. If I try to delete B, it is deleting B but not affecting in the UI. In the UI it is showing only the last item is deleted that is C has been deleted but in console B has been deleted.
Here is my code:
<FormControl style={{ display: "flex", flex: 1 }}>
<FormGroup>
{inputList.map((x, i) => {
return (
<div key={i}>
<div className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.isCorrect}
control={<Checkbox />}
checked={x.isCorrect}
onChange={(e) => handleIsCorrect(e, i)}
/>
) : (
""
)}
<div className="editor">
<BubbleEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
/>
</div>
{distributePoints ? (
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
className={styles.inputCounter}
/>
) : (
""
)}
{inputList.length !== 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
)}
</div>
);
})}
</FormGroup>
</FormControl>
Remove function:
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
Add function:
const handleAddClick = () => {
setInputList([...inputList, { answer: "", isCorrect: false, points: 0 }]);
};
State array of object that is being used:
const [inputList, setInputList] = useState([
{ answer: "", isCorrect: false, points: 0 },
]);
While updating a state property, using its previous value, the callback argument should be used.
This is due to the possible asynchronous nature of state updates
var handleRemoveClick = (index)=> {
setInputList((list)=> (
list.filter((o,i)=> (index != i))
);
};
var handleAddClick = ()=> {
setInputList((list)=> (
list.concat({answer:'', isCorrect: false, points: 0})
);
};
Related
I have a list of dynamically created inputs which are saved in state. When i remove an input field, it is removed correctly, However, when after deleting an input field i add a new input field, new value is not added to list, infact the input that was just deleted appears. I am using ingredient name as key, can it cause issue since it has spaces int it? Below is my code
const [savedingredients, setIngredients] = useStat([]);
const saveIngredient = async () => {
var name = document.getElementById("ing_name1").value;
reset({ ...getValues(), ing_name1: "" });
var ingredients_array = savedingredients;
ingredients_array.push({ name: name });
var newArray = [...ingredients_array];
setIngredients(newArray);
};
const deleteIngredient = (indexArray) => {
var idNum = parseInt(indexArray) + 1;
var nameId = "ing" + idNum;
var checkIngredients = [];
savedingredients.map((inst, i) => {
if (i != indexArray) {
checkIngredients.push({ name: inst.name });
}
});
setIngredients([...checkIngredients]);
enqueueSnackbar("Ingredient Deleted", {
variant: "success",
});
};
<List>
{savedingredients.map((ing, i) => (
<ListItem key={ing.name}>
<Grid container spacing={1}>
<Grid item xs={6} md={6}>
<Controller
name={`ing${i + 1}`}
defaultValue={ing.name ? ing.name : ""}
control={control}
rules={{
required: true,
minLength: 3,
}}
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id={`ing${i + 1}`}
inputProps={{ type: "text", disabled: true }}
{...field}
></TextField>
)}
></Controller>
</Grid>
<Grid item xs={3} md={2}>
<IconButton
onClick={() => deleteIngredient(i)}
aria-label="close"
sx={{
position: "absolute",
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
edge="end"
>
<CloseIcon></CloseIcon>
</IconButton>
<IconButton
onClick={(e) => editIngredient(e, i)}
aria-label="toggle action"
sx={{
position: "absolute",
right: 30,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
edge="start"
>
{stepAction == "Edit" ? <Edit /> : <Save />}
</IconButton>
</Grid>
</Grid>
</ListItem>
))}
</List>
const [fvalues,setFvalues]=useState(data.map((ele,id)=>{
return(
{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}
);
}));
I want to update these objects when there is a change in input tag values
let handleChange = (e)=>{
let {name,value}=e.target;
data.map((ele,id)=>{
// return setFvalues({ ...fvalues[id], [name]: value });
setFvalues(fvalues[id].name)
})
// setFvalues(fvalues[0].name=value)
console.log(name,value);
}
but this logic is not working
I have mapped forms and want to submit all the forms with one submit button, I want to update the input values which is entered by the users
{datas.map((ele, id) => {
let val = id + 1;
return (
<>
<Box key={id}>
{/* <HealthAndContactPass key={id} fun={handelSubmit} psName={ele?.psName} address={ele?.address} /> */}
{/* <HealthAndContactPassForm errors={errors} handleSubmit={handleSubmit} register={register} id={id} psName={ele?.psName} address={ele?.address} onSubmit={onSubmit}/> */}
<Typography className={styles.psName}>{ele.psName}</Typography>
<Box className={styles.white_box}>
<Box className={styles.form_flex}>
<Box className={styles.mobile}>
<Select className={classes.select} name='countryCode' defaultValue={'+91'} value={code} {...register("code")}>
<MenuItem className={styles.code_id} value={'+91'}>+91</MenuItem>
<MenuItem className={styles.code_id} value={'+25'}>+25</MenuItem>
<MenuItem className={styles.code_id} value={'+12'}>+12</MenuItem>
<MenuItem className={styles.code_id} value={'+13'}>+13</MenuItem>
</Select>
<TextField helperText={ferrors?.mobile} value={fvalues[id].mobile} name="mobile" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Mobile Number" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].emailId} name="emailId" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="Email Id" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].age} name="age" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="age" variant="outlined" onChange={handleChange} />
</Box>
</Box>
<Box className={styles.form_flex2}>
<TextField value={fvalues[id].destinationAddress} name="destinationAddress" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationAddress" variant="outlined" onChange={handleChange} />
<TextField value={fvalues[id].destinationPin} name="destinationPin" classes={{ root: classes.textField }} InputProps={{ className: classes.textField }} label="destinationPin" variant="outlined" onChange={handleChange} />
</Box>
<Box className={styles.hr}></Box>
{id===0?(
<Box className={styles.addres}>
<ThemeProvider theme={theme}>
<Checkbox className={classes.check} {...label} />
</ThemeProvider>
<Typography className={styles.selectAdd}>Select same address for all</Typography>
</Box>):null
}
</Box>
</Box>
</>
)
})}
In this case you don't "update" the state array per se, rather you create a clone of the state array then modify the values you want and set the state to be this cloned array. I wasn't quite sure what exactly you wanted to do to the array, but see the general example below:
const [state, setState] = useState([{mobile:'',age:'',emailId:'',destinationAddress:'',destinationPin:''}]);
let handleChange = e => {
const {name, value} = e.target;
const stateClone = state.map((item, i) => ({...state[i], [name]: value }))
// do what you want to this new array
setState(stateClone); // update the state array with the new values
}
If you want to update one column of a row, you can create a callback that will take an index (that you'll get when you will render the array of rows) and that will return a callback that will take the event (triggered when the event is dispatched by the browser) and that will update your value.
Then, you only need to trigger a new state change by cloning the old array (using Array.prototype.map) and mapping the new value of the row at any given index. If the index does not match, this means that the row that is mapped is not concerned by the change event so we return the row as-is.
import React, {useState, useCallback} from "react";
const App = () => {
const [rows, setRows] = useState([
{id: 1, value: ""},
{id: 2, value: ""},
{id: 3, value: ""}
]);
const handleRowValueChange = useCallback(index => ({currentTarget: {value}}) => {
setRows(rows.map((row, rowIndex) => {
if (rowIndex === index) {
return {
...row,
value
};
}
return row;
}));
}, [rows]);
return (
<table>
<tbody>
{rows.map((row, index) => (
<tr key={row.id}>
<td>
<input value={row.value} onChange={handleRowValueChange(index)} />
</td>
</tr>
))}
</tbody>
</table>
);
};
export default App;
Update your handleChange function definition to
let handleChange = (e)=>{
let {name,value}=e.target;
const newData = data.map((ele,id)=>{
return { ...fvalues[id], [name]: value };
// setFvalues(fvalues[id].name)
})
setFvalues(newData);
console.log(name,value);
}
Here, you will first create a new array (newData) using data.map and then assign the same as the new state using the setFvalues call.
Currently, you are calling setFvalues inside data.map because of which the state is being updated again and again with an individual array element (an object, in your case) on each iteration of the map method.
I'm coding a infinite inputs component to add bill items in a page, so the user can add how many items row as he wants. But now I have a crucial problem, when I click on "cancel" button for clean all the inputs and show just 1 row of items I show the previous state of this first row. How can I clear all the inputs and then show the placeholder in the first row of items? Thanks
PrincipalComponent.tsx:
type BillingManualProps = LinkStateProps & LinkDispatchProps;
const BillingManual: React.FC<BillingManualProps> = ({...}) => {
return (
<>
<section className="entity__main-container">
<div className="billing-manual-items-to-bill">
<h2 className="billing-title-row">Indique los ítems a facturar:</h2>
<BillingItems
selectedShipper={selectedShipper}
selectedShipperSettings={selectedShipperSettings}
billItems={billItems}
setOpenBillItem={setOpenBillItem}
setUpdateBillItem={setUpdateBillItem}
setNewBillItem={setNewBillItem}
setRemoveBillItem={setRemoveBillItem}
/>
</div>
<div className="billing-manual-shadow">
{billItems && (
<IonLabel
className="shipper_document_add_new_document button-border"
onClick={() => setNewBillItem()}
>
+ NEW ITEM
</IonLabel>
)}
</div>
<div className="billing-manual__buttonsGroup">
<GalaxyButton
color="medium"
className="GalaxyButton"
onClick={() => {
resetPropsFn();
}}
>
CANCEL
</GalaxyButton>
</div>
</section>
</>
);
};
resetProps action:
export const resetPropsFn = () => {
return async (dispatch: Dispatch) => {
dispatch(billingManualSlice.actions.resetPropsFn());
};
};
resetProps Slice:
resetPropsFn: (state) => {
return {
...state,
isFetching: false,
selectedShipper: null,
selectedShipperSettings: null,
billItems: [
{
item: null,
amount: null,
},
],
};
},
Billin child component:
<div className="billing-manual-scroll">
{!!billItems &&
billItems.map((document, id) => {
return (
<div key={id} className="billing-manual-items-container">
<IonRow className="input-delete-container" key={id}>
<IonCol className="billing-manual-column" size="6">
<IonItem style={{ marginTop: 4 }}>
<IonLabel color="primary" position={'stacked'}>
Ítem {id + 1}*
</IonLabel>
<TextField
name={'item'}
defaultValue={null}
id="billing-item-input"
variant="standard"
fullWidth={true}
disabled={
!(!!selectedShipper && !!selectedShipperSettings)
}
placeholder={'Indique el ítem a facturar'}
value={document.item ? document.item : null}
onChange={(event: any) => {
setUpdateBillItem(
id,
ValuesType.ITEM,
event.target.value
);
}}
/>
</IonItem>
</IonCol>
<IonCol className="billing-manual-column" size="6">
<IonItem style={{ marginTop: 4 }}>
<IonLabel color="primary" position={'stacked'}>
Monto*
</IonLabel>
<TextField
name={'amount'}
defaultValue={null}
style={{ backgroundColor: '#F5F5F5' }}
fullWidth={true}
type={'number'}
disabled={
!(!!selectedShipper && !!selectedShipperSettings)
}
placeholder={'0.00'}
value={
!!selectedShipper && !!selectedShipperSettings
? document.amount
: null
}
onChange={(event: any) => {
setUpdateBillItem(
id,
ValuesType.AMOUNT,
event.target.value
);
}}
InputProps={{
type: 'search',
startAdornment: (
<InputAdornment position="start">
{' '}
<AttachMoney fontSize={'small'} />
</InputAdornment>
),
}}
/>
</IonItem>
</IonCol>
</IonRow>
</div>
);
})}
</div>
You can set all the input states/hooks to empty string after completing the desired action
I am trying to develop a generic filter component which can have many fields to filter on like color,
size, price range etc and each field might have different types of elements like color may have
checkboxes, radio button and price range might have input element, dropdown etc. To support such varied
cases, I tried to go with this pattern but here I have to iterate the same things multiple times.
I am not sure of this data structure. If anyone has suggestion please help me to improve this code but
the main problem here is "multiple iteration". How can i improve this code?
const filterParams = {
field: {
id : 1, label : 'Field', content: <FieldFilter />
},
employee: {
id : 1, label : 'Employee', content: <Employee />
}
}
<Filter filterParams={filterParams} activeFilterParam="field" />
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
return (
<>
<Button secondary icon={filter} onClick={() => setShow(!show)}>Filter</Button>
{show && (
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List
render={() => {
return (
Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
)
}))
}} />
<Tabs.Panels>
{Object.keys(filterParams).map(filterParam => {
return (
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
)
})}
</Tabs.Panels>
</Tabs>
</Card.Content>
<Card.Footer>
<Button>
<Button.Content style={{ marginRight: 10 }}>Save</Button.Content>
<Button.Content secondary onClick={()=>setShow(!show)}>Cancel</Button.Content>
</Button>
</Card.Footer>
</Card>
)}
</>
)
}
If you're not liking the multiple calls to Object.keys(filterParams).map, you could move the loop to the top of the component function. Something like the below might work:
const Filter = ({ filterParams, activeFilterParam }) => {
const [show, setShow]=useState(false)
const {tabs, panels} = Object.keys(filterParams)
.reduce((acc, filterParam) => {
acc.tabs.push(
<Tabs.Tab key={filterParam} id={filterParam}>{filterParams[filterParam].label}</Tabs.Tab>
);
acc.panels.push(
<Tabs.Panel key={filterParam} panelId={filterParam}>{filterParams[filterParam].content}</Tabs.Panel>
);
return acc;
}, { tabs: [], panels: [] });
return (
...
<Card style={{ marginTop: 10 }}>
<Card.Content>
<Tabs activeTab={activeFilterParam}>
<Tabs.List render={() => tabs} />
<Tabs.Panels>
{panels}
</Tabs.Panels>
</Tabs>
...
</Card>
...
)
}
Note that I haven't run this - it likely won't be quite right, but should give the general idea...
I trying to add new button which would add new tabs
I'm using react-tabs
which build tabs like this
<Tabs>
<TabList>
<Tab>Title 1</Tab>
<Tab>Title 2</Tab>
</TabList>
<TabPanel>
<h2>Any content 1</h2>
</TabPanel>
<TabPanel>
<h2>Any content 2</h2>
</TabPanel>
</Tabs>
so I need two loop one for the tab and another one for tabpanel
like this
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
})}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>
The state is stats.
Each item in the stats array looks something like this { figure: '100k', instructions:'humans'}
The button "add new stat" just appends a new stat object to this array and calls setAttributes.
The remove button just removes the item at that index.
It doesn't give any errors but there isn't any tab added when I click on add new stat button
You are not returning anything from the function given to map. Either return it or change the function body ({}) to parens (()) to make the return implicit:
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => { // return statement
return <Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => ( // implicit return
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
))}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>