How to handle old state reducer react - reactjs

I am creating a cart where the client has multiple checkboxes, I can update the cart when the checkbox is checked and update it when is unchecked, so far so good, but the problem is when the client wants to create another order, I think the reducer is bringing me the old order.
This is my reducer
if (action.type === ADD_ITEM_TO_CART_DETAILS) {
return {
...state,
cart: [...state.cart, action.payload]
}
}
This is my action
export function addItemDetail(value){
return {
type : ADD_ITEM_TO_CART_DETAILS,
payload: value
}
}
and this is the JS:
export default function DetailProduct() {
const dispatch = useDispatch();
const { id } = useParams();
const history = useHistory();
useEffect(() => {
dispatch(getDetail(id));
}, [dispatch, id]);
const detail = useSelector((state) => state.detail); // details of the products
const { options, setOptions } = useContext(OrderContext);
const BackToProducts = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/productos");
}
};
const seeCart = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/carrito");
}
};
const handleComments = (e) => {
setOptions((prev) => ({
...prev,
Comments: e.target.value,
}));
};
const handleSalsa = (e) => {
const { name, checked } = e.target;
if (options.salsa.length <= 2) {
e.target.checked = false;
} else if (checked === true) {
setOptions((prev) => ({
...prev,
salsa: [...prev.salsa, name],
picture_url: detail.picture_url,
id: uuidv4(),
price: detail.price,
title: detail.title,
}));
}
if (checked === false) {
setOptions((prev) => ({
...prev,
salsa: prev.salsa.filter((p) => p !== name),
}));
}
};
const handleToppings = async (e) => {
const { name, checked } = e.target;
if (checked === true) {
setOptions({ ...options, toppings: [...options.toppings, name] });
}
if (checked === false) {
setOptions((prev) => ({
...prev,
toppings: prev.toppings.filter((p) => p !== name),
}));
}
};
useEffect(() => {
// useEffect to update the total amount
const productPrice = options.price; // price of the single product
const toppingPrice = options.priceTopping; // price of the topping
const total = toppingPrice ? productPrice + toppingPrice : productPrice;
setOptions((prev) => ({ ...prev, unit_price: total })); // set total amount product plus toppings
}, [options.price, options.priceTopping, setOptions]);
useEffect(() => {
// useEffect to update total amount of the toppings
const numberOfToppings = options.toppings.length;
const totalPriceTopping = numberOfToppings !== 0 ? numberOfToppings * 119 : 0;
setOptions((prev) => ({ ...prev, priceTopping: totalPriceTopping }));
}, [options.toppings, setOptions]);
return (
<MainContainer>
{detail.picture_url ? <PhotoProduct src={`https://hit-pasta.herokuapp.com/${detail.picture_url}`} /> : <Loading />}
<Like
onClick={() => {
history.push("/productos");
}}
/>
<ContainerOption>
{detail &&
detail?.salsas?.map((p, index) => {
return (
<ContainerOptionChild key={index}>
<div>
<Drop />
<LabelProductName>{p.sauce}</LabelProductName>
</div>
<InputOptions type="checkbox" checked={options.salsa.index} key={index} name={p.sauce} value={p.sauce} onChange={handleSalsa} />
<Description>{p.description}</Description>
</ContainerOptionChild>
);
})}
</ContainerOption>
<MainBoxComments>
<h3>Comentarios</h3>
<BoxComentario type="text" value={options.Comments} onChange={handleComments} placeholder="Agrega instrucciones o comentarios a tu orden" />
</MainBoxComments>
<MainBoxBtns>
<Okay onClick={seeCart}>
OKAY <CartIcon />
</Okay>
<BtnArmarOtroHit onClick={BackToProducts}>ARMAR OTRO HIT</BtnArmarOtroHit>
</MainBoxBtns>{" "}
</MainContainer>
);
}

Related

How to update dynamic multiple input (user can add those input themself)?

I have a form. Initially there is some default values (user name and address). When user click add, there is an extra input which user can enter another name and address, and the extra name and address will store in additionConfigs.
Example:
https://codesandbox.io/s/elastic-pateu-2uy4rt
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const [value, setValue] = useState([]);
const [additionConfigs, setAdditionConfigs] = useState([]);
useEffect(() => {
setTimeout(() => {
setValue([
{
id: 1,
baseName: "XXX",
config: {
name: "Kenny",
address: "New york"
}
},
{
id: 2,
baseName: "YYY",
config: {
name: "Ben",
address: "Boston"
}
},
{
id: 3,
baseName: "ZZZ",
config: {
name: "Mary",
address: "Los Angeles"
}
}
]);
}, 1000);
}, []);
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => [
...preValue,
{
id: item.id,
config: {
name: "",
address: ""
}
}
]);
};
console.log(additionConfigs);
const onChangeName = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return newValue;
});
};
const onChangeAddress = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return newValue;
});
};
return (
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs.length > 0 &&
additionConfigs
.filter((config) => config.id === v.id)
.map((config) => (
<div>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>
);
}
Currently, I use config.id to update the extra name and address, but there is an issue that if user add two or more extra name and address input, when updating the first one, the second will update, too.
How do I update respectively? Giving each group of input a flag?
Assuming that the component should not modify the base value as it is set by a useEffect, but keep a additionConfigs which need to support any amount of config inputs, perhaps one solution could be to make additionConfigs state an object.
The additionConfigs object could have id from base value as key and an array of configs as value, and perhaps each config need its own id, so that they can be controlled by the added input, without major refactor of the existing code structure.
Forked live with modifications: codesandbox
Perhaps try the following as an example:
Define additionConfigs state as an object:
const [additionConfigs, setAdditionConfigs] = useState({});
Update logic for additionConfigs when adding a config input:
(The id logic here is only adding previous id++, and should probably be replaced by a unique id generator in actual project)
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => {
const preConfigs = preValue?.[item.id];
const newId = preConfigs
? preConfigs.reduce((acc, cur) => (cur.id > acc ? cur.id : acc), 0) + 1
: 1;
return {
...preValue,
[item.id]: preConfigs
? [
...preConfigs,
{
id: newId,
config: {
name: "",
address: ""
}
}
]
: [
{
id: newId,
config: {
name: "",
address: ""
}
}
]
};
});
};
Update logic for a config input for name, a baseId is added as an argument as each base value can have multiple configs:
const onChangeName = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Same but for address:
const onChangeAddress = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Output with the changes:
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs?.[v.id] &&
additionConfigs?.[v.id].length > 0 &&
additionConfigs?.[v.id].map((config, index) => (
<div key={config.id}>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id, v.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id, v.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>

Is there a way to handle state in a form that is dynamically built based off of parameters sent from the back end

I have a page in react 18 talking to a server which is passing information about how to build specific elements in a dynamic form. I am trying to figure out how to manage state in a case where there are multiple selects/multiselects in the page. Using one hook will not work separately for each dropdown field.
Code is updated with the latest updates. Only having issues with setting default values at this point. Hooks will not initially set values when given.
import { Calendar } from 'primereact/calendar';
import { Dropdown } from 'primereact/dropdown';
import { InputSwitch } from 'primereact/inputswitch';
import { InputText } from 'primereact/inputtext';
import { MultiSelect } from 'primereact/multiselect';
import React, { useEffect, useState, VFC } from 'react';
import { useLocation } from 'react-router-dom';
import { useEffectOnce } from 'usehooks-ts';
import { useAppDispatch, useAppSelector } from 'redux/store';
import Form from '../components/ReportViewForm/Form';
import { getReportParamsAsync, selectReportParams } from '../redux/slice';
export const ReportView: VFC = () => {
const location = useLocation();
const locState = location.state as any;
const dispatch = useAppDispatch();
const reportParams = useAppSelector(selectReportParams);
const fields: JSX.Element[] = [];
const depList: any[] = [];
//const defaultValList: any[] = [];
//dynamically setting state on all dropdown and multiselect fields
const handleDdlVal = (name: string, value: string) => {
depList.forEach((dep) => {
if (name === dep.dependencies[0]) {
dispatch(getReportParamsAsync(currentMenuItem + name + value));
}
});
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
//dynamically setting state on all calendar fields
const handleCalVal = (name: string, value: Date) => {
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
//dynamically setting state on all boolean fields
const handleBoolVal = (name: string, value: boolean) => {
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
/* function getInitVals(values: any) {
const defaultList: any[] = [];
values.forEach((param: any) => {
defaultList.push({ name: param.name, value: param.defaultValues[0] });
});
} */
const [state, setState] = useState<any>({});
const [currentMenuItem, setCurrentMenuItem] = useState(locState.menuItem.id.toString());
useEffectOnce(() => {}), [];
useEffect(() => {
if (reportParams?.length === 0) {
dispatch(getReportParamsAsync(currentMenuItem));
}
//reload hack in order to get page to load correct fields when navigating to another report view
if (currentMenuItem != locState.menuItem.id) {
window.location.reload();
setCurrentMenuItem(locState.menuItem.id.toString());
}
}, [dispatch, reportParams, currentMenuItem, locState, state]);
//dependency list to check for dependent dropdowns, passed to reportddl
reportParams.forEach((parameter: any) => {
if (parameter.dependencies !== null && parameter.dependencies[0] !== 'apu_id') {
depList.push(parameter);
}
});
//filter dispatched data to build correct fields with data attached.
reportParams.forEach((parameter: any, i: number) => {
if (parameter.validValuesQueryBased === true) {
if (parameter.validValues !== null && parameter.multiValue) {
const dataList: any[] = [];
parameter.validValues.map((record: { value: any; label: any }) =>
dataList.push({ id: record.value, desc: record.label }),
);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<MultiSelect
options={dataList}
name={parameter.name}
value={state[parameter.name]}
onChange={(e) => handleDdlVal(parameter.name, e.value)}
></MultiSelect>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.validValues !== null) {
const dataList: any[] = [];
parameter.validValues.map((record: { value: any; label: any }) =>
dataList.push({ id: record.value, desc: record.label }),
);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<Dropdown
options={dataList}
optionValue='id'
optionLabel='desc'
name={parameter.name}
onChange={(e) => handleDdlVal(parameter.name, e.value)}
value={state[parameter.name]}
//required={parameter.parameterStateName}
placeholder={'Select a Value'}
></Dropdown>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
}
} else if (parameter.parameterTypeName === 'Boolean') {
fields.push(
<span key={i} className='col-12 mx-3 field-checkbox'>
<InputSwitch
checked={state[parameter.name]}
id={parameter.id}
name={parameter.name}
onChange={(e) => handleBoolVal(parameter.name, e.value)}
></InputSwitch>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.parameterTypeName === 'DateTime') {
//const date = new Date(parameter.defaultValues[0]);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<Calendar
value={state[parameter.name]}
name={parameter.name}
onChange={(e) => {
const d: Date = e.value as Date;
handleCalVal(parameter.name, d);
}}
></Calendar>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.name === 'apu_id') {
return null;
} else {
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<InputText name={parameter.name}></InputText>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
}
});
const onSubmit = () => {
console.log(state);
};
return (
<Form onReset={null} onSubmit={onSubmit} initialValues={null} validation={null} key={null}>
{fields}
</Form>
);
};
enter code here

React map not rendering array

After obtaining a data, I take a photo of the user from the users table with the user's ids, I can access all the data without any problem and I can see all of them with console.log. But it doesn't render.
How can i solve this problem ? Thank you.
export default function MentionInput(props) {
const [state, dispatch] = useContext(UsePeopleModuleContext);
const [decisionUserRole, setDecisionUserRole] = useContext(
UseDecisionUserRoleContext
);
const { isConsulted, isResponsible, isApprover, isCreator } =
decisionUserRole;
const { currentUser } = useAuth();
const { users, currentWorkspace, activeDecision, setActiveDecision } =
useWorkspace();
const [value, setValue] = useState([]);
const [loading, setLoading] = useState(true);
const userMentionData = users.map((myUser) => ({
id: myUser.userId,
value: myUser.userId,
displayName: `${myUser.displayName}`,
label: `${myUser.displayName}`,
}));
const propsData = props;
useEffect(async () => {
setLoading(true);
const people = await usersModule.getPeople(
currentWorkspace,
activeDecision.id
);
const datas = [];
if (propsData.field === 'consulted') {
people.consulted.map((consulted) => {
console.log('CONSULTED =>', consulted);
userdb.getUser(consulted.id).then((user) => {
const photoURL = user.photoURL;
datas.push({
id: consulted.id,
value: consulted.id,
displayName: `${user.displayName}`,
label: `${user.displayName}`,
photoURL: photoURL !== undefined ? photoURL : '',
});
});
});
console.log('DATAS =>', datas);
setValue(datas);
setLoading(false);
} else if (propsData.field === 'approver') {
const user = await userdb.getUser(people.approver.id);
const photoURL = user.photoURL;
setValue([{ ...people.approver, photoURL: photoURL }]);
setLoading(false);
} else if (propsData.field === 'responsible') {
//await userdb.getUser
const user = await userdb.getUser(people.responsible.id);
const photoURL = user.photoURL;
setValue([{ ...people.responsible, photoURL: photoURL }]);
setLoading(false);
}
}, []);
return (
!loading &&
(isCreator || !isResponsible ? (
<Box w="100%">
<Select
isMulti={
props.field === 'consulted'
? true
: props.field === 'approver'
? false
: props.field === 'responsible'
? false
: true
}
name=""
options={userMentionData}
placeholder="Assign people"
closeMenuOnSelect={false}
size="sm"
onChange={handleChange}
value={value}
/>
</Box>
) : (
<Box w="100%" ml="10px">
<Wrap>
{value?.map((item, index) =>
item !== undefined ? (
<WrapItem key={index}>
<Tag
size="md"
borderRadius="lg"
ml={index === 0 ? 0 : 2}
colorScheme="gray"
>
<Avatar
size="xs"
name={
item?.displayName !== undefined &&
item?.displayName.toLowerCase()
}
ml={-3}
mr={2}
src={item.photoURL}
/>
<TagLabel>{item?.displayName}</TagLabel>
</Tag>
</WrapItem>
) : null
)}
</Wrap>
</Box>
))
);
}
userdb.getUser: it works beautifully.
getUser: async (userId) => {
const q = query(doc(db, 'user', userId));
const getData = await getDoc(q);
if (getData.exists()) {
return getData.data();
}
},
As you can see from the screenshots, the data comes out fine, but the component does not render.
i update my setState function , the question solved.
if (propsData.field === 'consulted') {
const datas = [];
people.consulted.map((consulted) => {
userdb.getUser(consulted.id).then((user) => {
const photoURL = user.photoURL;
setValue((value) => [
...value,
{
id: consulted.id,
value: consulted.id,
displayName: `${user.displayName}`,
label: `${user.displayName}`,
photoURL: photoURL !== undefined ? photoURL : '',
},
]);
});
});
setLoading(false);
}

how to test context in the test file ?And how to access values from context in test file?

I am new to react context and testing. I have used react context in my application but now I want to test the application but I don't know how to access values from context in test file. I am attaching component and context file.
My component file:
import React, { useContext } from 'react'
import constants, {
parametersEnum,
commandParametersEnum,
commandTypeEnum,
} from '../../../constants'
import Parameters from '../Parameters'
import { OTAFormContext } from '../../utils/Context/OTAFormContext'
export const componentTestId = 'SelectorNetwork'
function OTARequestCommandParameters(props) {
const { formData, updateFormData } = useContext(OTAFormContext)
let command = formData.COMMAND_TYPE
if (!command) return null
const commandObject = constants.OTARequestCommands.choices.find(
(_p) => _p.value === command,
)
console.log('formData==>', formData) //remove later
const supportedParameters = commandObject.parameters
let parameters = supportedParameters.map((parameter) => {
let onChange = null
let options = null
let preFilledValue = null
switch (command) {
case commandTypeEnum.CHANGE_PREFERRED_PLMN: {
if (
parameter.type === parametersEnum.textAreaInput &&
parameter.value === commandParametersEnum.NEW_PREFERRED_PLMN
) {
preFilledValue = formData.CHANGE_PREFERRED_PLMN.NEW_PREFERRED_PLMN
onChange = (e) => {
updateFormData({
...formData,
CHANGE_PREFERRED_PLMN: { NEW_PREFERRED_PLMN: e.target.value, },
})
}
}
break
}
case commandTypeEnum.CHANGE_SMSC_NUMBER: {
if (
parameter.type === parametersEnum.inputTextBox &&
parameter.value === commandParametersEnum.NEW_SMSC_NUMBER
) {
preFilledValue = formData.CHANGE_SMSC_NUMBER.NEW_SMSC_NUMBER
onChange = (e) => {
updateFormData({
...formData,
CHANGE_SMSC_NUMBER: { NEW_SMSC_NUMBER: e.target.value },
})
}
}
break
}
case commandTypeEnum.FORCED_PREFERRED_IMSI_SWITCH: {
if (
parameter.type === parametersEnum.textAreaInput &&
parameter.value === commandParametersEnum.NEW_FULL_PREFERRED_IMSI_FILE
) {
preFilledValue = formData.FORCED_PREFERRED_IMSI_SWITCH.NEW_FULL_PREFERRED_IMSI_FILE
onChange = (e) => {
updateFormData({
...formData,
FORCED_PREFERRED_IMSI_SWITCH: { NEW_FULL_PREFERRED_IMSI_FILE: e.target.value, },
})
}
}
break
}
case commandTypeEnum.MODIFY_PREFERRED_IMSI: {
if (
parameter.type === parametersEnum.textAreaInput &&
parameter.value === commandParametersEnum.NEW_FULL_PREFERRED_IMSI_FILE
) {
preFilledValue = formData.MODIFY_PREFERRED_IMSI.NEW_FULL_PREFERRED_IMSI_FILE
onChange = (e) => {
updateFormData({
...formData,
MODIFY_PREFERRED_IMSI: { NEW_FULL_PREFERRED_IMSI_FILE: e.target.value, },
})
}
}
break
}
case commandTypeEnum.SYNC_AN_IMSI: {
options = parameter.options
if (
parameter.type === parametersEnum.dropdown &&
parameter.value === commandParametersEnum.ACTION_TO_SYNC
) {
preFilledValue = formData.SYNC_AN_IMSI.ACTION_TO_SYNC
onChange = (e, data) => {
updateFormData({
...formData,
SYNC_AN_IMSI: {
...formData.SYNC_AN_IMSI,
ACTION_TO_SYNC: data.value,
},
})
}
}
if (
parameter.type === parametersEnum.dropdown &&
parameter.value === commandParametersEnum.TARGET_IMSI_PROFILE
) {
preFilledValue = formData.SYNC_AN_IMSI.TARGET_IMSI_PROFILE
onChange = (e, data) => {
updateFormData({
...formData,
SYNC_AN_IMSI: {
...formData.SYNC_AN_IMSI,
TARGET_IMSI_PROFILE: data.value,
},
})
}
}
break
}
}
const _arguments = {
parameterName: parameter.type,
onChange,
label: parameter.label,
placeholder: parameter.placeholder,
options,
preFilledValue: preFilledValue,
}
return <Parameters {..._arguments} />
})
return <div data-testid={componentTestId}>{parameters}</div>
}
export default OTARequestCommandParameters
My context file:
import React, { useState, createContext } from 'react'
import constants from '../../../constants'
export const OTAFormContext = createContext()
const OTAFormContextProvider = (props) => {
let defaultFormData = constants.defaultContextFormData
const [formData, setFormData] = useState(defaultFormData)
const updateFormData = (data) => {
setFormData(data)
}
return (
<OTAFormContext.Provider value={{ formData, updateFormData }}>
{props.children}
</OTAFormContext.Provider>
)
}
export default OTAFormContextProvider
Constants.js:
defaultContextFormData: {
COMMAND_TYPE: '',
ICCID: '',
CHANGE_PREFERRED_PLMN: {
NEW_PREFERRED_PLMN: '',
},
CHANGE_SMSC_NUMBER: {
NEW_SMSC_NUMBER: '',
},
FORCED_PREFERRED_IMSI_SWITCH: {
NEW_FULL_PREFERRED_IMSI_FILE: '',
},
MODIFY_PREFERRED_IMSI: {
NEW_FULL_PREFERRED_IMSI_FILE: '',
},
SYNC_AN_IMSI: {
ACTION_TO_SYNC: '',
TARGET_IMSI_PROFILE: '',
},
},
Test:
const _constants = constants.OTARequestCommands
let _network = _constants.choices.map((item) => [item.value, item.label])
test.each(_network)(
'Verify that passing selectedNetwork %j is correctly reflected',
(name) => {
const formData = { COMMAND_TYPE: name }
const component = render(
<OTAFormContext.Provider value={formData}>
<OTARequestCommandSelector />
</OTAFormContext.Provider>,
)
const { queryAllByText } = component
queryAllByText(name).forEach((item) => {
const { getByText } = within(item)
expect(getByText(name)).toBeInTheDocument()
})
},
)
Error:
TypeError: Cannot read property 'COMMAND_TYPE' of undefined
22 | onChange: onCommandChange,
23 | options: options,
> 24 | value: formData.COMMAND_TYPE,
| ^
25 | }
26 | return (
27 | <div className="otaRequestCommandSelector" data-testid={componentTestId}>
How to test OTARequestCommandParameters component? How to test context in the test? I have attached Test and Error.
You just render the context with your component.
const formData = { COMMAND_TYPE: 'Type anything you expect' }
const component = render(
<OTAFormContext.Provider value={{ formData }}>
<OTARequestCommandSelector />
</OTAFormContext.Provider>,
)
See Mocking Context with React Testing Library

Unable to update nested state react

I have a reusable drop down menu component and i render it twice with two different lists and it should update the state with the id of the first element.
the first drop down of the layout update the state without any issue but the second one does not(i switched the order and it always seems the first one updates the state the second doesn't).
please see code
dashbord
const initializeData = {
actionStatuses: [],
actionCategories: [],
actionGroups: [],
actionEvents: [],
actionEventsWithFilter: [],
selectedFilters: {actionStatusId: "", actionCategoryId:""},
};
const Dashboard = ({ selectedPracticeAndFy }) => {
const [data, setData] = useState(initializeData);
const getSelectedStatus = ({ key }) => {
const actionStatusId = key;
const selectedFilters = { ...data.selectedFilters, actionStatusId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
const getSelectedCategory = ({ key }) => {
const actionCategoryId = key;
const selectedFilters = { ...data.selectedFilters, actionCategoryId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
}
result filter:
const ResultFilter = ({actionStatuses, actionCategories, getSelectedStatus, getSelectedCategory}) => {
return (
<Grid
justify="flex-start"
container
>
<Grid item >
<Typography component="div" style={{padding:"3px 9px 0px 0px"}}>
<Box fontWeight="fontWeightBold" m={1}>
Result Filter:
</Box>
</Typography>
</Grid>
<Grid >
<DropdownList payload={actionCategories} onChange={getSelectedCategory} widthSize= {dropdownStyle.medium}/>
<DropdownList payload={actionStatuses} onChange={getSelectedStatus} widthSize= {dropdownStyle.medium}/>
</Grid>
</Grid>
);
}
DropdownList:
const DropdownList = ({ label, payload, onChange, widthSize, heightSize, withBorders, initialData }) => {
const { selectedData, setSelectedData, handelInputChange } = useForm(
payload
);
useEffect(() => {
if (Object.entries(selectedData).length === 0 && payload.length !== 0) {
setSelectedData(payload[0]);
}
}, [payload]);
useEffect(() => {
if (Object.entries(selectedData).length !== 0) {
onChange(selectedData);
}
}, [selectedData]);
return (
<div style={widthSize}>
<div className="ct-select-group ct-js-select-group" style={heightSize}>
<select
className="ct-select ct-js-select"
id={label}
value={JSON.stringify(selectedData)}
onChange={handelInputChange}
style={withBorders}
>
{label && <option value={JSON.stringify({key: "", value: ""})}>{label}</option>}
{payload.map((item, i) => (
<option key={i} value={JSON.stringify(item)} title={item.value}>
{item.value}
</option>
))}
</select>
</div>
</div>
);
};
It may be a stale closure issue, could you try the following:
const getSelectedStatus = ({ key }) => {
setData((data) => {
const actionStatusId = key;
const selectedFilters = {
...data.selectedFilters,
actionStatusId,
};
return { ...data, selectedFilters };
});
};
const getSelectedCategory = ({ key }) => {
setData((data) => {
const actionCategoryId = key;
const selectedFilters = {
...data.selectedFilters,
actionCategoryId,
};
return { ...data, selectedFilters };
});
};

Resources