State arrays (not objects) and the spread operator - reactjs

I am updating a sub array in a react state array of 'components' with a new key value like this.
this.setState({
components: {
...this.state.components,
[this.state.key]: {
...this.state.components[this.state.key],
[key]: v
}
}
});
this works but it changes this.state.components from an array into an object which I don't want.
I can do
var result = Object.keys(this.state.components).map(function (k) {
return { [k]: this.state.components[k] };
});
this.setState({components: result});
to fix the data but it seems messy and inefficient to set state twice. Is there a better way? I've tried various forms of using [] instead of {}, but from my understanding of the spread operator this should work.

You can use map on the components array currently in state and return the object as is if the key in the state doesn't match the component index, or change the [key] property if they do match.
this.setState(prevState => ({
components: prevState.components.map((c, index) =>
prevState.key === index ? { ...c, [key]: v } : c
)
}));

Related

array declaration in this.state React

**I'm trying to create an array with 5 values which I could use with nameArray[number].
I think that the declaration of the array is wrong but I don't know how I can fix it.
My idea is that: I have 5 buttons, when I click one of this, only one value of the 5 values in the state array change from false to true.
**
constructor(props) {
super(props);
this.state = {
activeButtons: [false, false, false, false, false]
};
}
cliccato = (e) => {
e.preventDefault();
const number = parseInt(e.target.id);
this.setState(
{
activeButtons: !this.state.activeButtons[number],
},
() => {
console.log(" "+ this.state.activeButtons[number]);
}
);
}
You're updating your state's activeButtons with a single boolean value, rather than an updated array.
You need to generate a new array and modify only the relevant element:
const newArray = [...this.state.activeButtons];
newArray[number] = !newArray[number];
this.setState({
activeButtons: newArray,
});
Declaration of the array is fine. You can make it shorter with Array(5).fill(false).
It's setting state part that needs work. In your current code, you are setting the state to the alternate of a boolean value, instead you need to set it to an array.
this.setState(prevState => ({
activeButtons: prevState.activeButtons.map((val, index) => {
if(index === number) {
return !val;
}
return val;
})
}));
Also, using the functional set state form here
It's because you're overwriting activeButtons every time with only one element value. You need to preserve the other values each time you want to update an element.
Using an Object would be a more graceful data structure in this case though.
this.setState(
{
activeButtons: {
...this.state.activeButtons
[number]: !this.state.activeButtons[number]
}
)

React redux updates flatlist item but turns item into an Array

I am using react redux with react native, I have a flatlist of photos. When I click the like button, I'd like to update only a single photo in the flatlist. The following code seems to work, and the photo's like status is updated, but somehow my feed is messed up after the update. Before the update, this.props.feed[index] is a single object, after the update, this.props.feed[index] becomes an array of objects, what am I doing wrong? I got the idea from: https://stackoverflow.com/a/47651395/10906478
But it also seems really inefficient to loop through all items of flatlist to find one that matches the passed in photoId. Is there a better way?
Screen:
toggleLike = async(photoId, index) => {
console.log("before: ", this.props.feed[index]);
await this.props.toggleLike(photoId);
console.log("after: ", this.props.feed[index]);
}
...
<FlatList
data = {this.props.feed}
keyExtractor = {(_, index) => index.toString()}
renderItem = {({item, index}) => (
<View key={index}>
<TouchableOpacity onPress={()=> this.toggleLike(item.photoId, index)}>
<Text>Button</Text>
</TouchableOpacity>
</View>
)}
/>
Action
export const toggleLike = (photoId) => async(dispatch) => {
dispatch({type: "UPDATE_ITEM", photoId: photoId})
}
Reducer
export default (state = initialState, action) => {
switch(action.type) {
case "UPDATE_ITEM":
return {...state, feed: [state.feed.map((item,_) => {
if (item.photoId === action.photoId) {
return { ...item, liked: !item.liked };
}
return { ...item };
})]};
// other cases
You're calling map inside an array, which returns a nested array:
return {...state, feed: /* Here ->*/[state.feed.map((item,_) => {
if (item.photoId === action.photoId) {
return { ...item, liked: !item.liked };
}
return { ...item };
})]};
This should do:
return {
...state, // Current state
feed: state.feed.map((item, _) => { // map returns a new array
if (item.photoId === action.photoId) {
item.liked = !item.liked;
}
return item;
})
}
For the feed being an array, look closely at your code. You'll see you have wrapped the value of feed in square brackets and run a map on the array. So feed is an array, and the map is also an array. This is why you have an array of objects at each index point for state.feed. I'd normally suggest you get rid of the surrounding square brackets and let your map create the array.
However, that isn't actually the root cause of the issue, and there is a more thorough solution.
If the need is to find the matching ID and update the "liked" value without disturbing the order of the array, try using findIndex instead of map on the array. Find the index where your item is and update just that value. You might need to make a clone of the array and inner objects if it complains about directly mutating a Redux store value.
Best of luck!

How to prepend a value to an array inside the object of state in Reactjs

I have an initial state as:
this.state = {
transition:{
completedStages:[]
}
}
What I am trying to achieve is, at a function call I can update my state for completedStages, such that every time the state is updated the value is prepended in the array. Like:
this.state = {
transition:{
completedStages:[SR, RR, BR]
}
}
For this, what I tried is:
let completedStages = [...this.state.transition.completedStages];
completedStages.unshift('SR');
this.setState({
transition:{
completedStages:[completedStages]
}
})
This is messing up my output and giving every added array value in pair of other as key. How can I understand this scenario?
You can try:
this.setState((state) => {
return {
transition: {
...state.transition,
completedStages: ['SR', ...state.transition.completedStages]
}
}
})
Just prepend it like this. You can use the function way of setState either:
this.setState(prevState => ({
transition:{
completedStages:['SR', ...prevState.transition.completedStages]
}})

Better use of conditional render with SetState (React Native)

I'm rendering a list of products according to a specific value. I'm doing the render with a Picker Component and when it's different of 306, I'm loading the selected products but IF I come back on the first PickerItem (306) I want to show ALL the products again...
For instance :
if (Value != '306') {
this.setState({
// isAll: false,
labels,
Colours: Value,
filteredProducts: this.state.displayProducts.filter(product => product.colour == Value)
}, () => {
this.onLoadFilteredLabels();
});
} else {
this.setState({
// isAll: true,
Colours: Value,
displayProducts: this.state.products,
displayLabels: this.state.labels
});
}
I'm looking for some advice if there is a better way of doing this ?
Do you think I should separe every setState ?
It's working but I have the feeling that it's a bit tricky and I'm still learning. So I know I can have goods advices here with people here !
Thank you
The easiest approach is to create a custom object for setting states and just passing arguments in a custom method. Apart from that, using ternary could be beneficial:
let stateObj = value === '306' ? {obj : {
// isAll: true,
Colours: Value,
displayProducts: this.state.products,
displayLabels: this.state.labels
}, func : () => {
return false;
}} : {obj : {
// isAll: false,
labels,
Colours: Value,
filteredProducts: this.state.displayProducts.filter(product => product.colour == Value)
}, func : () => {
this.onLoadFilteredLabels();
}}
this.stateSetter(stateObj);
Next define your custom method:
stateSetter = (stateObj) => {
let {obj, func} = stateObj;
this.setState(obj,func);
}
You can make use of ternary operator, equality check etc. So that your code will look clean. For example,
this.setState({
isAll: Value === '306',
filterProducts: Value === '306' ? [yourFilterProducts] : []
});
Generally, it's ok if you have several setState() calls in different branches of a condition.
But in your case, it's better to update in the state only Colours and filter products directly inside the render method:
render() {
const {products: allProducts, Colours} = this.state;
const displayProducts = Value != '306' ? allProducts.filter(product => product.colour == Colours) : allProducts;
return (
<div>
{displayProducts.map(product => <YourProductComponent key={product.id} product={product}/>)}
<div>
);
}
React documentation recommends that if you can calculate required data from state or/and props you should do it instead of introducing new prop or state variable.
PS: there's a common recommendation to not to refer to this.state when in setState().
React runs setState in batch so you can end up with referring to outdated state. You should pass a function as the first argument of setState. More details here.

Change state in array of objects

I'm confused.
How change specific element in array of object? For example, i want change 'isComplete' in object with id=1
Anything like this? In this case your new state depends on the old state so you have to use the setState's function parameter version.
this.setState(prevState => {
const updatedTasks = prevState.task.map(task => {
return (task.id === 1 ? Object.assign({}, task, {isComplete: !task.isComplete}) : task)
})
return {task: updatedTasks}
})

Resources