react native how to merge two arrays with a function - javascript

I have two functions and when I press each of them I create an array.
this.state = {
food:[],
sports:[],
interest:[],
}
_favoriteFood(foodState){
const food = foodState
this.setState({food:food})
console.log(food)
console.log(this.state.food)
}
_favoriteSports(SportsState){
const sports = SportsState
this.setState({sports:sports})
console.log(sports)
console.log(this.state.sports)
}
render(){
return (
<View>
<FavoriteFood favoriteFood={this._favoriteFood}/>
</View>
<View>
<FavoriteSports favoriteSports={this._favoriteSports}/>
</View>
)}
So for example, I am getting arrays like food:[pizza, hodog] and sports:[basketball, surfing] when I call a method by pressing a button.
My question is when I try to merge two arrays like:
const interest = [...this.state.food, ...this.state.sports]
Its showing undefined because I think I am calling it before the render happens.
Should I make another method to merge arrays?
Any advice or comments would be really helpful. Thanks in advance :)

You problem can happen because React doesn't change the state immediately when you call setState, it may change the state later. If you want to access the state after React applies the change, you should use the second argument of the setState method:
_favoriteFood(food){
this.setState({food}, () => {
const interest = [...this.state.food, ...this.state.sports];
});
}
Reference
The other solution is to use your method argument instead of reading the same value from this.state:
_favoriteFood(food){
this.setState({food});
const interest = [...food, ...this.state.sports];
}
BTW, you should not store const interest = [...this.state.food, ...this.state.sports] in the state, because it can be derived from the other state variables.

Push multi item in array
Merge Two Array
var subject1=['item1','item2']
var subject=['item3','item4','item5'];
subject1.push(...subject)
console.log(subject1);
//output
['item1', 'item2', 'item3', 'item4', 'item5']

Related

How to conveniently use a useState value in a stored function?

I have a component that stores an array of items in a useState which are later displayed. This allows me to update the list and rerender it. I'm trying to create functions I can store and send to other components to allow them to sort my list and have the result displayed. Unfortunately, when I create and store a function, it only uses the initial value of the useState.
For example, look at my code below:
export default function MyWindow ({someAccessor}) {
const [objectList, setObjectList] = useState([])
const sortObjects = () => {
let newList = [...objectList]
newList.sort(someSortFunction)
setObjectList(newList)
}
const createAndSortObjects = () => {
let newList = [1, 2, 3, etc.]
newList.sort(someSortFunction)
setObjectList(newList)
}
useEffect(() => {
populateObjectListFunction() //Line 1
someAccessor.passFunction(sortObjects) //Line 2
someAccessor.passFunction(createAndSortObjects ) //Line 3
}, [])
return (
<div>
{objectList.mapTo(someComponentMap)}
</div>
)
}
In Line 3, if the createAndSortObjects funtion is called by the accessor, it is able to create a new array, sort it as needed, and then update my objectList variable. If I try to do the same with Line 2, however, it only uses the inital value of objectList, which is [], and replaces the array populated in Line 3.
How can I conveniently fix this issue, and have Line 2 update the existing item? I think I could probably use a useRef and access the .current value in sortObjects, but this would mean I need two separate variables to keep track of one object. I also can't switch from my useState because then the components won't get rerendered when the list changes. What should I do?

Can I define a variable within setState in React js?

I am still new to React js.
I am trying to use useState({}) to define an object of objects of orders.
For the newOrderHandler, I am passing the order to be added.
The idea is to add a new object if the order title does not exist and update the amount if the order title already exists in the orders state.
This is the code:
const [orders, setOrders] = useState({});
const newOrderHandler = (newOrder) => {
setOrders(prevOrders => {
console.log('prevOrderss', prevOrders)
// console.log(`prevOrders[newOrder.title]`, prevOrders[newOrder.title])
let newOrders = prevOrders;
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
});
};
The problem here is that although when I log the prevOrders to the console, I get it as I wanted:
However, when I calculate the number of objects in the Navigation component, it just displays 0 always.
This is the code that calculates the number of objects in the Navigation component:
Your Cart <span>{Object.keys(props.orders).length}</span>
This is how I passed the props to the Navigation component:
<Navigation orders={orders} />
This always displays 0. I guess the problem is when defining this: let newOrders in the setOrders function, but I am not sure how to solve it.
Thanks in advance.
The problem is that you React cannot detect that you have changed the object. You need to make a copy, you are passing in the same reference.
newOrders == prevOrders returns true.
What is standard is to make a copy so that you do not mutate the state and react can detect that the object has actually changed.
You can use the spread operator.
let newOrders = { ...prevOrders, [newOrder.title] : { ...prevOrders[newOrder.title] }};
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
Spreading the nested property too because you are mutating its amount property. For every level of nesting you will have to use spread for the property you want to change.

How to add dynamic input values to the local state for retrieval

I have a React Native form that allows me to add an Input UI in the form, by clicking a button with this function. This allow me to generate it on the fly. The code for that is this.
addClick() {
this.setState(prevState => ({ values: [...prevState.values, ""] }));
console.log(this.values[0].name);
}
That part works well, but I'm having a problem extracting the data from the dynamic inputs, and add it to an array. So far I have tried this
setVal = value => {
const values = this.state.values[0];
if (values[0].name === "" || values[0].description === "") return;
[...this.state.values, value];
this.setState(values);
console.log(values);
};
How do I organize my states properly so that I can add as many inputs I need, and when I'm finished, I can update the state, and access the new data in my list component?
How do I update my state to the new Array? at the moment, this.state only shows the initial state set at the top.
I'm missing a few things
Please take a look at the full code sandbox HERE so you can see:
See...your created isssue is not so obvious we need to see where you call setVal() function but....
i think you will be more comfortable if you render your <input/> s directly from your state array, not from const x = [] variant. because it seems like you want a dynamic view and in such a cases you will need to bind your loop quantity from state. so:
this.state = {
x: [0,1,2,3,4]
}
and inside your render :
{this.state.x.map(x => {
return (
<TextInput
placeholder={`name${x}`}
value={values[x.toString()]}
handleBlur={() => handleBlur(x.toString())}
onChangeText={handleChange(x.toString())}
style={styles.input}
/>
);
})}

ReactJS Array.push function not working in setState

I'm making a primitive quiz app with 3 questions so far, all true or false. In my handleContinue method there is a call to push the users input from a radio form into the userAnswers array. It works fine for the first run of handleContinue, after that it throws an error: Uncaught TypeError: this.state.userAnswers.push is not a function(…)
import React from "react"
export default class Questions extends React.Component {
constructor(props) {
super(props)
this.state = {
questionNumber: 1,
userAnswers: [],
value: ''
}
this.handleContinue = this.handleContinue.bind(this)
this.handleChange = this.handleChange.bind(this)
}
//when Continue button is clicked
handleContinue() {
this.setState({
//this push function throws error on 2nd go round
userAnswers: this.state.userAnswers.push(this.state.value),
questionNumber: this.state.questionNumber + 1
//callback function for synchronicity
}, () => {
if (this.state.questionNumber > 3) {
this.props.changeHeader(this.state.userAnswers.toString())
this.props.unMount()
} else {
this.props.changeHeader("Question " + this.state.questionNumber)
}
})
console.log(this.state.userAnswers)
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
render() {
const questions = [
"Blargh?",
"blah blah blah?",
"how many dogs?"
]
return (
<div class="container-fluid text-center">
<h1>{questions[this.state.questionNumber - 1]}</h1>
<div class="radio">
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="true"
onChange={this.handleChange}/>True
</label><br/><br/>
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="false"
onChange={this.handleChange}/>False
</label>
<hr/>
<button type="button" class="btn btn-primary"
onClick={this.handleContinue}>Continue</button>
</div>
</div>
)
}
}
Do not modify state directly! In general, try to avoid mutation.
Array.prototype.push() mutates the array in-place. So essentially, when you push to an array inside setState, you mutate the original state by using push. And since push returns the new array length instead of the actual array, you're setting this.state.userAnswers to a numerical value, and this is why you're getting Uncaught TypeError: this.state.userAnswers.push is not a function(…) on the second run, because you can't push to a number.
You need to use Array.prototype.concat() instead. It doesn't mutate the original array, and returns a new array with the new concatenated elements. This is what you want to do inside setState. Your code should look something like this:
this.setState({
userAnswers: this.state.userAnswers.concat(this.state.value),
questionNumber: this.state.questionNumber + 1
}
Array.push does not returns the new array. try using
this.state.userAnswers.concat([this.state.value])
this will return new userAnswers array
References: array push and array concat
You should treat the state object as immutable, however you need to re-create the array so its pointing to a new object, set the new item, then reset the state.
handleContinue() {
var newState = this.state.userAnswers.slice();
newState.push(this.state.value);
this.setState({
//this push function throws error on 2nd go round
userAnswers: newState,
questionNumber: this.state.questionNumber + 1
//callback function for synchronicity
}, () => {
if (this.state.questionNumber > 3) {
this.props.changeHeader(this.state.userAnswers.toString())
this.props.unMount()
} else {
this.props.changeHeader("Question " + this.state.questionNumber)
}
})
console.log(this.state.userAnswers)
}
Another alternative to the above solution is to use .concat(), since its returns a new array itself. Its equivalent to creating a new variable but is a much shorter code.
this.setState({
userAnswers: this.state.userAnswers.concat(this.state.value),
questionNumber: this.state.questionNumber + 1
}
The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:
this.setState(prevState => ({
userAnswers: [...prevState.userAnswers, this.state.value]
}));
I have found a solution. This shoud work for splice and others too. Lets say that I have a state which is an array of cars:
this.state = {
cars: ['BMW','AUDI','mercedes']
};
this.addState = this.addState.bind(this);
Now, addState is the methnod that i will use to add new items to my array. This should look like this:
addState(){
let arr = this.state.cars;
arr.push('skoda');
this.setState({cars: arr});
}
I have found this solution thanks to duwalanise. All I had to do was to return the new array in order to push new items. I was facing this kind of issue for a lot of time. I will try more functions to see if it really works for all functions that normally won't. If anyone have a better idea how to achieve this with a cleaner code, please feel free to reply to my post.
The correct way to mutate your state when you want to push something to it is to do the following. Let's say we have defined a state as such:
const [state, setState] = useState([])
Now we want to push the following object into the state array. We use the concat method to achieve this operation as such:
let object = {a: '1', b:'2', c:'3'}
Now to push this object into the state array, you do the following:
setState(state => state.concat(object))
You will see that your state is populated with the object.
The reason why concat works but push doesn't is because of the following
Array.prototype.push() adds an element into original array and returns an integer which is its new array length.
Array.prototype.concat() returns a new array with concatenated element without even touching in original array. It's a shallow copy.

Does updating 1 element in an array trigger a render in React for other elements in array?

I am using React with Redux for my current project.
I have a state object like so:
state: {
reducerOne: {
keyOne: valueOne,
keyTwo: valueTwo
}
reducerTwo: {
keyThree: valueThree
keyFour: valueFour
}
}
Suppose valueFour is an array of objects. I use the valueFour array to map a bunch of React elements like so in the render method:
this.props.valueFour.map(()=> <div/>)
(Obviously the above is simplified but all that I am trying to indicate is that I am rendering a bunch of elements)
Now, I would like to update only 1 of the above elements. In my action/reducer code I return the entire valueFour array with the 1 element updated in a function like so:
action code
export function updateThingInArray(elementId){
return (dispatch, getState) => {
const myArray = getState().reducerTwo.keyFour
for (let i=0; i < myArray.length; i++) {
if (myArray[i].id === elementId){
const result= [
...myArray.slice(0,i),
Object.assign({},
myArray[i],
{field:newValue}),
...myArray.slice(i+1)
]
dispatch(update(result))
}
}
}
}
reducer code:
export default handleActions({
[UPDATE]: (state,{payload}) => Object.assign({},state, {keyFour: payload})
},{
keyThree:{},
keyFour:{}
})
(My reducer code uses the library redux-actions)
I hope all this makes sense. If it doesn't please ask and I will clarify.
But my question is, since keyFour has the entireArray technically updated, does that mean ALL my elements will update even though only 1 has new data? If so, is there a more efficient way of implementing what I am currently doing?
The render method will be called again, which means it will map through that array again. You can look at the elements tab in chrome and you can see the html that flashes, it is the part that changed. If the component has the same data and the same key, the actual html won't be changed, but the render method will be called in the children.
Edit: didn't realize you were updating a nested key, React only checks shallow equality: https://facebook.github.io/react/docs/shallow-compare.html

Categories