Update nested React state? - reactjs

I'm trying to update part of a state object that is nested. This is the object:
const [buttonObject, setButtonObject] = useState({
intro: [
{
id: '123',
name: 'first_intro_name',
selected: false,
},
{
id: '124',
name: 'second_intro_name',
selected: false,
},
],
experience: [
{
id: '789',
name: 'first_experience_name',
selected: false,
},
{
id: '8910',
name: 'second_experience_name',
selected: false,
},
],
});
When a button is clicked I want to toggle the selected state. I'm using a click handler that looks like this:
const handleButtonClick = ({ id, selected }) => {
if (id === '123') {
buttonsObject.intro.map(
pref => (pref.selected = pref.id === id ? !pref.selected : selected)
);
setButtonsObject(buttonsObject);
} else if (id === '124') {
buttonsObject.intro.map(
pref => (pref.selected = pref.id === id ? !pref.selected : selected)
);
setButtonsObject(buttonsObject);
}
};
It would handle experiences as well. The issue is that right now it seems like rather than updating the object it just overwrites the object or creates a new one. It also doesnt pass that information back down to the component even though I have it routed correctly so it should.
Is there better/correct syntax for updating nested state like this?
Thanks.

instead of checking again with if condition use higher order array function and spread operator to get optimal solution.
setButtonObject(previous => {
return {
...previous,
info: previous.info.map(item => item.id === id ? {
...item,
selected: true
} ? item)
}
})

Related

Finding an array item in state

I have the following array in my Sate:
this.state = {
currentAsset: null,
assets: [
{ id: uuidv4(), selected: false, text: 'Sorte',},
{ id: uuidv4(), selected: false, text: 'optical_dillusion2322.jpg'},
{ id: uuidv4(), selected: false, text: 'man_read_abook3242332.jpg'},
]
}
I want to find one of these assets and assign it to currentAsset. I have the following function:
handleAssetIDChange(assetID) {
console.log(assetID)
var currentAsset = this.state['assets'][assetID]
console.log(currentAsset)
// this.setState({
// currentAsset: this.state['assets'][assetID],
// openAssetSideBar: true
// }, () => {
// console.log(this.state['assets'][assetID])
// })
}
You can see the commented out part is now working. I am trying to set the currentAsset and then trigger the open of the sidebar to display the contents, but currentAsset is not getting set.
I have the assetID, how can I locate the one I need? Another question i have is in many stackoverflow posts they reference state vars like objects, aka:
this.state.assets but I always get an error when trying to do that.
How can I assign currentAsset and then trigger the openAssetSidebar when it has been set?
I would use a filter use get the object, this should update your currentAsset in the state.
handleAssetIDChange(assetID) {
console.log(assetID)
var currentAsset = this.state['assets'].filter( item => item.id === assetID)[0]
console.log(currentAsset)
this.setState(( prev ) => {
...prev,
currentAsset,
openAssetSideBar: true
})
}
In order to find the asset that you are looking for with a specific id, instead of
var currentAsset = this.state['assets'][assetID]
You should do:
const foundAsset = this.state['assets'].find((el) => el.id === assetID)
then to update the state, you would do:
this.setState((prev) => ({...prev, currentAsset: foundAsset}))

Semantic UI dropdown not setting value after selecting option

I am using React semantic ui. I am rendering a dropdown in Fieldset. I have written code such that, once a option is selected, the options is updated such that the selected option is removed from the list. But when I select an option from the dropdown, the selected value is not displayed, rather it shows empty.
Here is my code:
This is my dropdown code:
<Dropdown
name={`rows.${index}.mainField`}
className={"dropdown fieldDropdown"}
widths={2}
placeholder="Field"
fluid
selection
options={mainFieldOptions}
value={row.mainField}
onChange={(e, { value }) => {
setFieldValue(`rows.${index}.mainField`, value)
updateDropDownOptions(value)
}
}
/>
My options:
let mainField = [
{ key: "org", text: "org", value: "org" },
{ key: "role", text: "role", value: "role" },
{ key: "emailId", text: "emailId", value: "emailId" },
]
Also, I have:
const [mainFieldOptions, setMainFieldOptions] = useState(mainField)
And,
const updateDropDownOptions = (value:any) => {
let updatedOptions: { key: string; text: string; value: string }[] = []
mainFieldOptions.forEach(option => {
if(option.key != value){
updatedOptions.push({ key:option.key , text:option.key, value:option.key })
}
})
setMainFieldOptions(updatedOptions)
console.log("mainfield", mainField)
}
In onChange, if I dont call updateDropDownOptions() method, the dropdown value is set. But when I call the method, its giving blank value. Please help.
There are few changes required in your code,
You are pushing the entire initialValues when you are adding a row which is an [{}] but you need to push only {} so change your code to initialValues[0] in your push method.
Its not needed to maintain a additional state for the options. You can filter the options based on the selected option in other rows which is available in the values.rows .
Util for filtering the options
const getMainFieldOptions = (rows, index) => {
const selectedOptions = rows.filter((row, rowIndex) => rowIndex !== index);
const filteredOptions = mainField.filter(mainFieldOption => !selectedOptions.find(selectedOption => mainFieldOption.value === selectedOption.mainField));
return filteredOptions;
}
Call this util when rendering each row
values.rows.length > 0 &&
values.rows.map((row, index) => {
const mainFieldOptions = getMainFieldOptions(values.rows, index);
Working Sandbox

React Selecting dropdown option multiple times

I am wanting to be able to select a language I have called "Skip" more than one time without it disappearing from the dropdown. Currently, any language I select will disappear from the dropdown. Is this possible? Here's my code:
const languages = [
{ key: 'skip', text: 'Skip', value: 'Skip' },
{ key: 'english', text: 'English', value: 'English' },
{ key: 'french', text: 'French', value: 'French' },
]
handleAudioInputChange = (e, { name, value }) => this.setState( { [name]: value })
<Form.Select fluid search label='Audio Channels' name='audiochannelsValue' value={audiochannelsValue} options={languages} placeholder='Choose Audio Channels' onChange={this.handleAudioInputChange} multiple = "true"/>
I've tried multiple things such as hideSelectedOptions = {false}, but this does not seem to work. Any ideas?
If you only want a string based on the user input, you could do:
handleAudioInputChange = (e, { value }) => {
this.setState(prevState => {
const newVal = `${prevState.audiochannelsValue.length ? '/' : ''}${value}`;
return {
textValue: prevState.textValue.concat(newVal),
audiochannelsValue: value
};
});
}
This will build a string based on the previous state and separate each value with /. Haven't tested it, but it should generate (in order):
English
English/Skip
English/Skip/French
English/Skip/French/Skip

How to update a state which is a array of objects?

My state is as follows
this.state = {
todos: [{
title: 'asas',
status: 'incomplete',
uuid: 11
}, {
title: 'asas',
status: 'incomplete',
uuid: 12
}, {
title: 'asas',
status: 'complete',
uuid: 13
}],
currentTab: "Show All"
}
and whenever a user clicks on any of the todo items's checkBox i want to update the state status of the checkbox and i have written the following code for it
this.state.todos.map(todo => {
if (todo.uuid === uuid) todo.status = (todo.status === 'complete') ? 'incomplete' : 'complete'
});
this.forceUpdate();
Is Using forceUpdate a good approach here? as i have updating only a single value inside an array of objects. Is there a better solution for this problem?
either of the following will call setState with the updated state without modifying the current state.
https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#inserting-and-removing-items-in-arrays
using the spread operator:
edit.. actually, this is the hard way 8)
see https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#updating-an-item-in-an-array
this.setState(prevState => {
const idx = prevState.todos.findIndex(todo => todo.uuid === uuid);
return {
todos: [
...prevState.todos.slice(0, idx),
{
...prevState.todos[idx],
status: prevState.todos[idx].status === "complete" ? "incomplete" : "complete",
}
...prevState.todos.slice(idx + 1),
]
}
});
or using immer:
import produce from "immer";
this.setState(prevState => {
const idx = prevState.todos.findIndex(todo => todo.uuid === uuid);
return produce(prevState, draft => {
draft.todos[idx].status = prevState.todos[idx].status === "complete" ? "incomplete" : "complete"
});
});

updating the state based on condition and adding new property finally retrieving the updated state

whenever the button is clicked i am passing the id , so my state is having three objects
state = items:[{
id:1,
material: 'iron'
},
id:2,
material: 'steel'
},
id:3,
material: 'coal'
}]
//reducer.js
case actionTypes.UpdateMaterialToShow:
return {
...state,
items: state.items.map(obj => {
if(obj.id === action.payload.id){
obj.['show'] = action.payload.status
}
})
}
so whenever the method is invoked i am passing the id and status, so i need to add the property to the state.items
expected output is if the clicked button is iron
state = items:[{
id:1,
material: 'iron',
status: true
},
id:2,
material: 'steel'
},
id:3,
material: 'coal'
}]
how can i get back the updated state as shown above without mutating the state
When you use Array.map() a new array is created. Whenever the object's id is equal to the payload's id, you need to create a new object based on the old one, with the updated properties.
You can use Object.assign() or object spread (like the example) to create the new object:
case actionTypes.UpdateMaterialToShow:
return {
...state,
items: state.items.map(obj => {
if (obj.id === action.payload.id) {
return { ...obj,
status: action.payload.status
};
}
return obj;
})
}
You can use Reactjs#spread-attributes.
return {
...state,
items: state
.items
.map(obj => {
if (obj.id === action.payload.id) {
return {
...obj,
status: action.payload.status
}
} else {
return obj;
}
})
}

Resources