Storing State in React JS not working as excepted - reactjs

what i am creating here is a sorter with 3 inputs like shown here . This sorter will get some data from a table. Now i'm setting state with initSelect and i'm passing it the fields array but when i console.log(select) it gives me the object shown in the image which is incorrect from the behaviours i want {sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
The first input has to have a default value of 'Date' always , but it can change to other values in the dropdown list like name , username ect . When i console log the select state it is messed up as it's always selecting the last one on the fields array , how can i change the initSelects function to correctly build the object i want.
Also the tricky thing which i can't seem to do is , if this Date value is selcted , in the second input, the date value should not be there. And if in the second input we select another value like Earth , Earth and Date should not be in the 3rd input and so on. So basically it means filtering out values . I need serious help as this is for the company i work on
Excepted Behaviour: Dynamically update value every time i select one input element like
{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
When selectin 'Date' for example , it shouldn't not be included in the dropdownlist on sorterParam2, sorterParam3.
/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { GridSortModel } from '#mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
let object = {};
fields.map((item) => {
console.log(item, 'item');
object = {
...item,
[item.name]: item.defaultValue ? item.defaultValue : '',
};
});
return object;
};
const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
const fields: SorterProps[] = [
{
name: 'sorterParam1',
title: 'Sort by',
optional: false,
defaultValue: 'Date',
},
{
name: 'sorterParam2',
title: 'Then by',
optional: true,
},
{
name: 'sorterParam3',
title: 'Then by',
optional: true,
},
];
const [select, setSelect] = useState<any>(() => initSelects(fields));
const getMenuItems = useMemo(() => {
return menuItemsValue.filter((item) => select.sorterParam1 !== item);
}, [menuItemsValue, select]);
const handleSelectChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
console.log(111, name, value);
setSelect({ ...select, [name]: value });
setSortData(sortOptions);
};
const handleClearAllInputs = () => {
setSelect({
sorterParam1: '',
sorterParam2: '',
sorterParam3: '',
});
};
const handleConfirm = () => {};
return (
<TextField
label="Advanced Sorting"
className={styles.sorter__inputs}
id="sorter-parameter-1"
variant="standard"
InputProps={{
disableUnderline: true,
}}
select
SelectProps={{
IconComponent: (props) => <NewIcon {...props} />,
}}
sx={{
fontSize: '12px',
width: '100%',
'& .MuiInputBase-input:focus': {
backgroundColor: 'transparent !important',
},
'& .MuiInputLabel-root': {
color: '#9E9E9E',
},
'& .MuiTextField-root': {
fontSize: '13px',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#fff',
},
}}
>
{fields.map((option, index) => (
<SorterField
key={option.name}
menuItemsValue={getMenuItems}
name={option.name}
option={option}
count={fields.length}
handleChange={handleSelectChange}
index={index + 1} // setData={setData}
/>
))}
<div className={styles.sorter__inputControllers}>
<Button
className={styles.sorter__clearAllInput}
onClick={() => handleClearAllInputs()}
>
Clear All
</Button>
<Button
onClick={() => handleConfirm()}
className={styles.sorter__confirmInput}
>
Confirm
</Button>
</div>
</TextField>
);
};
export default Sorter;
<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 is the SorterField Component code if that might be helpful
/*eslint-disable*/
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import { MenuItem } from '#mui/material';
import { SorterProps } from '../../types/Sorter';
import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';
type SorterFieldProps = {
menuItemsValue: string[];
option: SorterProps;
count: number;
name: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
index: number;
};
function SorterField({
option,
count,
menuItemsValue,
handleChange,
index,
}: SorterFieldProps) {
const handleSorting = () => {};
return (
<div className={styles.sorter__container}>
<div className={styles.sorter__header}>
<p className={styles.sorter__label}>
{option.title}{' '}
{option.optional && (
<sup className={styles.sorter__optional}>*Optional</sup>
)}
</p>
<div className={styles.sorter__numbers__container}>
{Array.from({ length: count }, (_, i) => i + 1).map((number) => (
<>
{number === index ? (
<>
<span className={styles.sorter__number}>{index}</span>
</>
) : (
<>
<span className={styles.sorter__numbers}>{number}</span>
</>
)}
</>
))}
</div>
</div>
<div className={styles.sorter__inputs}>
<TextField
className={[styles.sorter__input, styles.sorter__field__input].join(
' '
)}
variant="outlined"
label="Select"
select
SelectProps={{
IconComponent: () => <NewIcon />,
}}
value={option.defaultValue}
onChange={handleChange}
name={option.title}
size="small"
>
{menuItemsValue.map((title, idx) => (
<MenuItem key={idx} value={title}>
{title}
</MenuItem>
))}
</TextField>
<div onClick={handleSorting} className={styles.sorter__sortOrder}>
<div className={styles.sorter__sortOrder__alphabetical}>
<span>A</span>
<span>Z</span>
</div>
<div className={styles.sorter__sortOrder__sortLine}>
<SorterLine />
</div>
</div>
</div>
</div>
);
}
export default SorterField;
<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>

You're re-assigning a value to object in each iteration of fields.map().
If you want to build object as a map of field item name to defaultValue (or blank string), use this instead...
return Object.fromEntries(
fields.map(({ name, defaultValue }) => [name, defaultValue ?? ""])
);
See Object.fromEntries()
Also, move the fields declaration outside your component. It's static content so can be omitted from being declared every render and from any hook dependencies.
You could also use fields.reduce() which is basically the same thing
return fields.reduce(
(obj, { name, defaultValue }) => ({
...obj,
[name]: defaultValue ?? "",
}),
{}
);
As for removing selected options as you iterate through the fields, that's a little trickier.
You could use a memo hook to create an iterable data structure that includes the available options for that particular iteration.
For example
const fieldsWithOptions = useMemo(() => {
const taken = new Set(Object.values(select));
const available = menuItemsValue.filter((item) => !taken.has(item));
return fields.map((field) => ({
...field,
options: select[field.name]
? [select[field.name], ...available] // include the current selection
: available,
}));
}, [menuItemsValue, select]);
Then map over fieldsWithOptions instead of fields and use option.options instead of getMenuItems.

Related

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?

Nested state (react) with onclick events

I was given the task to build a recursive tree with some functionality. I managed to build a tree using a recursive component, I attach the code below.
function App() {
const [data, setData] = useState([{
id: 1,
name: "node 1",
children: [{
id: 2,
name: "node 1.1",
children: [{
id: 8,
name: "node 1.1.1",
children: []
}]
},{
id: 3,
name: "node 1.2",
children: [{
id: 4,
name: "node 1.2.1",
children: [{
id: 5,
name: "node 1.2.1.1",
children: []
}]
}]
}]
}])
return (
<div className="App">
<Tree data = {data} setData = {setData} margin={15}/>
<button>Add child</button>
</div>
);
}
and
const Tree = ({data, margin, setData}) => {
return (
<Fragment>
{data.map((element, index) => (
<div>
<div className="tier">
<div
style={{marginLeft: `${margin}px`}}
>
{element.name}
</div>
</div>
{element.children && element.children.length ? <Tree
data={element.children}
setData={setData}
margin={margin + 15}
/> : false}
</div>))}
</Fragment>
);
};
I need to add a node selection when clicking on it. I have an idea about the implementation: create a state in which the ID of the desired node will be stored, and design it using CSS.
But I have absolutely no idea how to add a child node to the selected node when the button is clicked.
You did half of the job and your idea about storing id is the best overall, you could actually store the selected item itself but it will be not as great as it sounds due to mutation of this item, you will not be able to trigger dependent hooks update for it.
So you only need to store the id of selected item and a few utility things to simplify the work with the tree. On the code below you can see the flatTree that was calculated with useMemo from the original data. It just flattens your tree to an array, so you will be able to easilly find real selected item and to calculate the next id that will be inserted on button click. And to add a few more props to the Tree component itself. onClick to handle actual click and selectedTreeItem just to add some classes for selected item (optional).
import "./styles.css";
import { Fragment, useEffect, useMemo, useState } from "react";
export default function App() {
const [data, setData] = useState([{id:1,name:"node 1",children:[{id:2,name:"node 1.1",children:[{id:8,name:"node 1.1.1",children:[]}]},{id:3,name:"node 1.2",children:[{id:4,name:"node 1.2.1",children:[{id:5,name:"node 1.2.1.1",children:[]}]}]}]}]);
const [selectedTreeItemId, setSelectedTreeItemId] = useState();
const flatTree = useMemo(() => {
const flat = (item) => [item, ...(item.children || []).flatMap(flat)];
return data.flatMap(flat);
}, [data]);
const selectedTreeItem = useMemo(() => {
if (!selectedTreeItemId || !flatTree) return undefined;
return flatTree.find((x) => x.id === selectedTreeItemId);
}, [flatTree, selectedTreeItemId]);
const getNextListItemId = () => {
const allIds = flatTree.map((x) => x.id);
return Math.max(...allIds) + 1;
};
const onTreeItemClick = ({ id }) => {
setSelectedTreeItemId((curr) => (curr === id ? undefined : id));
};
const onAddChildClick = () => {
if (!selectedTreeItem) return;
if (!selectedTreeItem.children) selectedTreeItem.children = [];
const newObj = {
id: getNextListItemId(),
name: `${selectedTreeItem.name}.${selectedTreeItem.children.length + 1}`,
children: []
};
selectedTreeItem.children.push(newObj);
// dirty deep-clone of the tree to force "selectedTreeItem"
// to be recalculated and its !reference! to be updated.
// so any hook that has a "selectedTreeItem" in depsArray
// will be executed now (as expected)
setData((curr) => JSON.parse(JSON.stringify(curr)));
};
useEffect(() => {
console.log("selectedTreeItem: ", selectedTreeItem);
}, [selectedTreeItem]);
return (
<div className="App">
<Tree
data={data}
margin={15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
<button
type="button"
disabled={!selectedTreeItem}
onClick={onAddChildClick}
>
Add child
</button>
</div>
);
}
const Tree = ({ data, margin, onTreeItemClick, selectedTreeItem }) => {
return (
<Fragment>
{data.map((element) => (
<div key={element.id}>
<div
className={`tier ${
selectedTreeItem === element ? "selected-list-item" : ""
}`}
onClick={() => onTreeItemClick(element)}
>
<div style={{ marginLeft: `${margin}px` }}>
{element.id}: {element.name}
</div>
</div>
{element.children?.length > 0 && (
<Tree
data={element.children}
margin={margin + 15}
onTreeItemClick={onTreeItemClick}
selectedTreeItem={selectedTreeItem}
/>
)}
</div>
))}
</Fragment>
);
};
Nested array in your date tree you should map as well. It should look like that
{element.children?.map(el, I => (
<p key={I}> {el. name}</p>
{el.children?.map(child, ind => (
<p key={ind}>{child.name}</p>
)}
)}
the ? is very important, because it would work only if there was a date like that.

Default value with react-select, checkboxes not working when I want to post my data having a blank page output

I'm not able to put my fetch data as a defaultValue in my Dropdown (react-select) and Checkboxe? I have the displayed dropdown ("Good",...) but the defaultValue for the dishId:1 is Medium, so I'm supposed to see Medium already selected in my Dropdown, which is not the case (same issue for comment).
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState([])
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes [0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
const COMMENT = [
{ label: "0", value: "0" },
...
];
function Checkbox({ value }) {
const [checked, setChecked] = React.useState(true);
return (
<label>
<input
type="checkbox"
defaultChecked={checked}
onChange={() => setChecked(!checked)}
/>
{value}
</label>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
<Dropdown
style={styles.select}
options={COMMENT}
defaultValue={TASTE.find((t) => t.label === dish.comment)}
isMulti={true}
/>
<Checkbox value={!!dish.trust} />
{(dish.menu) !== "") ?
<div>
Menu
<div >
{dish.menu}
</div>
</div>
: ""
}
</>
);
}
export default function CustomDropdown({
className,
style,
options,
styleSelect,
value,
setValue,
isMulti = false
}) {
const styles = {
select: {
width: "100%",
maxWidth: 200
}
};
function changeSelect(option) {
setValue(option.value);
}
return (
<div style={style} onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={value}
onChange={changeSelect}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
export default function Tag({ selected, setSelected, styleSelect }) {
const backgroundColor = styleSelect?.(selected?.label) ?? "grey";
return (
<div style={{
display: "flex",
justifyContent: "space-around",
padding: "0.1em",
backgroundColor: backgroundColor,
borderRadius: "4px",
color: "white",
marginRight: "20px",
marginLeft: "4px",
marginBottom: "8px"
}}>
{selected}
<button
style={{...}}
onClick={() => setSelected(null)}
>x</button>
</div>
)
}
I really don't understand why is it not working since dish.taste returns me "Medium", dish.comment returns me 5, dish.trust give me 1.
What's wrong with my Dropdown and/or Tag component?
Here there should be the main problem:
<Dropdown
defaultValue={TASTE.find((t) => t.label === dish.taste)}
/>
With this defaultValue prop you are only setting the value once, when the dropdown renders. The dish.taste can not be defined at that time if you retrieve its value through an async request. What you should do instead is adding a useEffect that sets the right value of the dropdown only after it has defined the dish.taste value. So:
const [valueDropdown, setValueDropdown] = React.useState(null);
useEffect(() => {
// here you are running this useEffect each time the dish state changes
if(dish?.taste){ //checks if taste exists
setValueDropdown(TASTE.find((t) => t.label === dish.taste));
}
}, [dish]);
Now, instead of passing to the dropdown the defaultValue you should pass two new props: the value and the setValue:
<Dropdown
value={valueDropdown}
setValue={setValueDropdown}
/>
Lastly, with the change of the props you should also update them inside the Dropdown component and remove the state definition inside that component:
export default function CustomDropdown({
style,
options,
styleSelect,
value : selected,
setValue : setSelected
}) {
// remove the line const [selected, setSelected] = useState(defaultValue);
....other logic
}
A possible fix:
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.find((d) => d.id === dishId))}
isMulti={true}
/>
The other part I could not undestand what you're trying to do, but it is wrong. As Cristiano replyied, you are comparing different types using !==, so it will never be a true condition. Maybe use the same fix as above?
(dish.find((d) => d.id === dishId).menu !== "")
EDIT AND REEDIT
Given more information, this could fix:
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState(null)
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
// make your dish variable not array
setDish(res.data.dishes[0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
if (!dish) {
return (
<div>Loading</div>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
// remove map cuz it is not array
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
{/* remove map cuz it is not array */}
{dish.menu !== "") ?
<div>
Menu
<div >
{question.map((e) => e.menu)}
</div>
</div>
: ""
}
</>
);
}
You are comparing a string (t.label) using === operator with array (result of dish.map) in defaultValue definition.
Try to change it.
Issue
I think the issue is simply that that defaultValue prop of a React component is expected to exist when the component mounts and never change. It's the default value when the component mounts. The code in your example is comparing an element in the TASTE array against the initial dish state, which is an array but accessed as an object. dish.taste of [] is undefined.
Solutions
Uncontrolled Component
If you want the Dropdown component to remain an uncontrolled component then it should be conditionally rendered only when the dish state has been initialized by the useEffect hook and the POST request.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
{dish.taste && ( // conditionally render the dropdown
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti
/>
)}
Controlled Component
If you want the Dropdown component to be controlled, then use the (likely) value prop instead. It's meant to be changed during the life of the component.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
<Dropdown
style={styles.select}
options={TASTE}
value={TASTE.find((t) => t.label === dish.taste)} // use value prop
isMulti
/>

React: Why is one of my prop variable undefined?

Hey I'm new to React and I'm having a problem with my prop which I'm passing to my ChildComponent. I am using "react-select" and have two multiselects. Originally I wanted to show in each select the value for the corresponding select I get from the state.
value={optionsColor.filter(item => ( myTest.color.includes(item.value)))}
But this is not possible because one of my calls is always "undefined". For example "myTest.Color" and "myTest.Car" one of them is "undefined" but I don't know why?
In my code (ChildComponent) I have two console.logs which illustrate this.
For example, if I select Color and have previously selected a car in Car, the console.log output looks like this.
undefined
blue
But I want it to output both.
import {useState} from "react";
import ChildComponent from "./ChildComponent";
const ParentComponent = () => {
const [step, setStep] = useState(0)
const [myTest, setMyTest] = useState(
{
color: ['defaultColor'],
car: ['defaultCar'],
}
)
const handleChange = (e, action) => {
setMyTest({ [action.name]: e ? e.map(x => x.value) : [] })
}
return (
<div>
<div className="card-body container mt-3">
<h2>Product data input Intended Use</h2>
<div className="card p-2 mt-5">
<form className="mb-4">
<div className="form"></div>
<ChildComponent myTest={myTest} handleChange={handleChange}/>
</form>
</div>
</div>
</div>
)
}
export default ParentComponent;
import React, { useState } from 'react';
import Select from 'react-select';
const optionsColor = [
{ value: 'blue', label: 'Blue' },
{ value: 'red', label: 'Red' },
{ value: 'yellow', label: 'Yellow' }
]
const optionsCar = [
{ value: 'bmw', label: 'BMW' },
{ value: 'ford', label: 'Ford' },
{ value: 'vw', label: 'VW' },
]
const ChildComponent = ({ handleChange, myTest}) => {
return (
<div>
<h4>Car {console.log(myTest.car)}</h4>
<Select
name="car"
options={optionsCar}
className="mb-3"
onChange={handleChange}
//value={intendedUse.sex === undefined ? '' : optionsSex.filter(item => (intendedUse.sex.includes(item.value)))}
isMulti
autoFocus
isSearchable
/>
<h4>Color {console.log(myTest.color)}</h4>
<Select
name="color"
options={optionsColor}
className="mb-3"
onChange={handleChange}
//value={intendedUse.age === undefined ? '': optionsAge.filter(item => ( intendedUse.age.includes(item.value)))}
isMulti
autoFocus
isSearchable
/>
</div>
)
}
export default ChildComponent;
the problem lies here.
setMyTest({ [action.name]: e ? e.map(x => x.value) : [] })
When you're updating your myTest state you're actually replacing both of the fields with the field you're setting.
Try something like this:
setMyTest(myTest => ({...myTest, { [action.name]: e ? e.map(x => x.value) : [] }}));
In that way, you have a new object with both the field that changed and the one that didn't.

Callback inside a useState updater function in react Hooks

Im new to hook and so is react,Ive been watching some tutorials lately,I saw Ben awad's video for dynamic forms and I tried replicating it.There he used a callback inside the useState updater function which seems new to me.He used link setPeople(currentPeople => {}) What is the argument currentPeople come from and why its used,Can you someone please explain,Thanks in advance!
import { useState } from "react";
import "./App.css";
import { generate } from "shortid";
interface Person {
id: string;
firstName: string;
lastName: string;
}
function App() {
const [people, setPeople] = useState<Person[]>([
{
id: "5",
firstName: "Aashiq",
lastName: "Ahmed",
},
]);
return (
<>
<h2 style={{ textAlign: "center" }}>Dynamic Form </h2>
<div style={{ textAlign: "center" }}>
<button
onClick={() => {
setPeople((currentPeople) => [
...currentPeople,
{
id: generate(),
firstName: "",
lastName: "",
},
]);
}}
>
add new person
</button>
{people.map((p, index) => {
return (
<div key={p.id}>
<input
placeholder="first name"
value={p.firstName}
onChange={(e) => {
const firstName = e.target.value;
setPeople((
currentPeople
) =>
currentPeople.map((x) =>
x.id === p.id ? { ...x, firstName } : x
)
);
}}
/>
<input
placeholder="last name"
value={p.lastName}
onChange={(e) => {
const lastName = e.target.value;
setPeople((currentPeople) =>
currentPeople.map((x) =>
x.id === p.id ? { ...x,lastName } : x
)
);
}}
/>
<button onClick={()=> setPeople( currentPeople =>currentPeople.filter(x=> x.id !== p.id) )}>x</button>
</div>
);
})}
<div>
<pre>{JSON.stringify(people, null, 3)}</pre>
</div>
</div>
</>
);
}
export default App;
No better explanation than the official one.
Here is the link: https://reactjs.org/docs/hooks-reference.html#usestate
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Your currentPeople is what the variable name suggests, the current value of the
const [people, setPeople] = useState<Person[]
For example:
You might send only one person that you want to add to your people's array, so you end up just attaching that Person to an already existing array of Persons.
Sure you could use setPeople([...people, newPerson]) but this wouldn't be correct in 100% of places because people might not have the latest value so the callback function comes to the rescue.
It is the current value of that state, this might come in handy, when you want to use the previous state to calculate the next state. An example would be a toggle function, that would toggle a modal or sth.
const [isOpen, setIsOpen] = useState(false);
const toggleIsOpen = () => setIsOpen(open=>!open);

Resources