array declaration in this.state React - reactjs

**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]
}
)

Related

Clearing inputs in React

I have a component in which I create a new post. I have a state used to configure and create a form. The main object in the state is called formControls and inside each element looks something like this:
title: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Title"
},
value: "",
validation: {
required: true
},
valid: false,
isTouched: false
}
Then I have a submit handler in which I create a new post and I try to clear the inputs. I m using 2 way binding so I try to clear by looping through state, make copies and update the values for each elements in the formControls : title, author and content like this:
for (let key in this.state.formControls) {
const updatedState = { ...this.state.formControls };
const updatedInput = { ...this.state.formControls[key] };
updatedInput.value = "";
updatedState[key] = updatedInput;
console.log(updatedState);
this.setState({
formControls: updatedState
});
}
The things is that it only clears the last element in the form (textarea). I console logged updatedState and in each iteration it clears the current input but in the next iteration the previous cleared input has again the value before clearing so only the last element is cleared in the end. If i move const updatedState = { ...this.state.formControls };
outside the for loop is behaves as it should. Does this happen because of async operation of setState() and it doesn t give me the right previous state when I try to update in each iteration?
I was hoping that maybe someone could help me understand why is like this. I would post more code but is quite long.
The data available to you in the closure is stale after the first call to setState. All iterations in the for .. in block will be run with the "old" data, so your last iteration is the one which is actually setting the fields to the values as they were when the for .. in loop began.
Try calling setState only once and put all your required changes into that.
const updatedFormControls = { ...this.state.formControls };
for (let key in this.state.formControls) {
const updatedInput = { ...updatedFormControls[key] };
updatedInput.value = "";
updatedFormControls[key] = updatedInput;
}
console.log(updatedFormControls);
this.setState({
formControls: updatedFormControls
});
Another way to do the same thing might look like this:
this.setState(state => ({
...state,
formControls: Object.keys(state.formControls).reduce(
(acc, key) => ({
...acc,
[key]: { ...state.formControls[key], value: '' }
}),
{}
)
});

State arrays (not objects) and the spread operator

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
)
}));

What is proper way to call function from onClick so it don't trigger wrong one?

When I click on button then onClick triggers correct function, run half through and jumps to other function which is not related to it and run through half of it and jumps back to first function, runs half trough again and drops error
Uncaught TypeError: _this.state.searchValue.toLowerCase is not a function
Interesting part is that I click other button before which triggers this function with toLowerCase() and there is no errors.
I dont have any idea whats going on here but so far i was trying to remove few lines to see which line cause it because I dont think that line with toLowerCase() realy is the reason. Everything works when I remove lines where is first this.setState.
Here is my function:
( Alerts is used to track where function is at, that how i know
that it run half through only. It never reach alert("DDD").
This function is which is triggered with button onClick like it should be )
onSelect = (e) => {
const data = e.target.getAttribute('data-id');
const itemId = e.target.getAttribute('data-id');
const itemIdState = !this.state[e.target.getAttribute('data-id')];
alert("AAA")
this.setState(state => { // <--- Somehow problem comes from this setState function
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
alert("CCC")
this.setState(State => ({
[itemId]: itemIdState,
}), function() {
alert("DDD")
if(this.state[itemId] === true){
this.setState({isAnySelected: true})
}else if(this.state[itemId] === false){
this.setState({isAnySelected: false})
}
})
}
This is other function which is triggered by mistake and is not related to other. It is just returning component which is displayed and when I press on its button then i have this issue.
filterSearch = (id, title, path) => {
let name = title.toLowerCase()
let filter = this.state.searchValue.toLowerCase()
if(name.includes(filter)){
return <SearchResult key={id} data-id={id} pName={path} onClick={this.onSelect} selected={this.state[id]} />
}
}
And here is from where filterSearch is triggered. Behind this.props.searchResult is Redux.
{this.props.searchResult ? this.props.searchResult.map(category =>
this.filterSearch(category.id, category.title, category.path)
) : null
}
I think I see what the problem is: in your problematic this.setState, you cast everything in your state to a boolean:
this.setState(state => {
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
Your for() statement ends up comparing searchValue to data (some kind of ID), which I imagine more often than not will not be the case, so searchValue ends up getting set to false.
And what happens when you try to do .toLowerCase() on a Boolean?
To fix this, consider structuring your state like this:
this.state = {
searchValue: '',
ids: {},
};
Then, replace your problematic this.setState with something like this:
this.setState((state) => {
const newIDs = {
// Create a clone of your current IDs
...state.ids,
};
Object.keys(newIDs).forEach(key => {
newIDs[key] = key === data
});
alert("BBB")
return {
// searchValue will remain untouched
...state,
// Only update your IDs
ids: newIDs,
}
});
What exactly are you wanting to do here?
this.setState(state => {
const newState = {}; // You are initializing an object
for (const dataId in state) {
newState[dataId] = dataId === data // You are putting in an array every property of state that is equal to data
}
return newState
});
So irrevocably, your this.state.searchValue property will be changed to something else, which is of boolean type. So toLowerCase being a function for string.prototype, you will get an error.
You should describe what you where aiming to get here.

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.

Resources