I seem to be having issues pushing data into a state array.
I am trying to achieve it this way:
this.setState({ myArray: this.state.myArray.push('new value') })
But I believe this is incorrect way and causes issues with mutability?
Using es6 it can be done like this:
this.setState({ myArray: [...this.state.myArray, 'new value'] }) //simple value
this.setState({ myArray: [...this.state.myArray, ...[1,2,3] ] }) //another array
Spread syntax
Array push returns length
this.state.myArray.push('new value') returns the length of the extended array, instead of the array itself.Array.prototype.push().
I guess you expect the returned value to be the array.
Immutability
It seems it's rather the behaviour of React:
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.React.Component.
I guess, you would do it like this (not familiar with React):
var joined = this.state.myArray.concat('new value');
this.setState({ myArray: joined })
Functional Components & React Hooks
const [array,setArray] = useState([]);
Push value at the end:
setArray(oldArray => [...oldArray,newValue] );
Push value at the start:
setArray(oldArray => [newValue,...oldArray] );
Never recommended to mutate the state directly.
The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:
Push string to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, "new value"]
}))
Push string to beginning of the array
this.setState(prevState => ({
myArray: ["new value", ...prevState.myArray]
}))
Push object to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, {"name": "object"}]
}))
Push object to beginning of the array
this.setState(prevState => ({
myArray: [ {"name": "object"}, ...prevState.myArray]
}))
You should not be operating the state at all. At least, not directly. If you want to update your array, you'll want to do something like this.
var newStateArray = this.state.myArray.slice();
newStateArray.push('new value');
this.setState(myArray: newStateArray);
Working on the state object directly is not desirable. You can also take a look at React's immutability helpers.
https://facebook.github.io/react/docs/update.html
Here you can not push the object to a state array like this. You can push like your way in normal array.
Here you have to set the state,
this.setState({
myArray: [...this.state.myArray, 'new value']
})
You can use .concat method to create copy of your array with new data:
this.setState({ myArray: this.state.myArray.concat('new value') })
But beware of special behaviour of .concat method when passing arrays - [1, 2].concat(['foo', 3], 'bar') will result in [1, 2, 'foo', 3, 'bar'].
Using react hooks, you can do following way
const [countryList, setCountries] = useState([]);
setCountries((countryList) => [
...countryList,
"India",
]);
This Code work for me :
fetch('http://localhost:8080')
.then(response => response.json())
.then(json => {
this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
})
React-Native
if u also want ur UI (ie. ur flatList) to be up to date, use PrevState:
in the example below if user clicks on the button , it is going to add a new object to the list( both in the model and UI)
data: ['shopping','reading'] // declared in constructor
onPress={() => {this.setState((prevState, props) => {
return {data: [new obj].concat(prevState.data) };
})}}.
In the following way we can check and update the objects
this.setState(prevState => ({
Chart: this.state.Chart.length !== 0 ? [...prevState.Chart,data[data.length - 1]] : data
}));
setState([...prevState, {
label: newState.name,
value: newState.id
}]);
Was working with the dropdowns and wanted to implement this scenario there, i found this simple solution for dropdown with multiple values.
you are breaking React principles, you should clone the old state then merge it with the new data, you shouldn't manipulate your state directly,
your code should go like this
fetch('http://localhost:8080').then(response => response.json()).then(json ={this.setState({mystate[...this.state.mystate, json]}) })
If you use:
const[myArr, setMyArr] = useState([]);
for add:
setMyArr([...myArr, value]);
and for remove:
let index = myArr.indexOf(value);
if(index !== -1)
setPatch([...myArr.slice(0, index), ...myArr.slice(index, myArr.length-1)]);
I guess this is a little bit late for an answer but for those new to react
You can use this tiny package called immer
see this example: https://immerjs.github.io/immer/produce
Related
I seem to be having issues pushing data into a state array.
I am trying to achieve it this way:
this.setState({ myArray: this.state.myArray.push('new value') })
But I believe this is incorrect way and causes issues with mutability?
Using es6 it can be done like this:
this.setState({ myArray: [...this.state.myArray, 'new value'] }) //simple value
this.setState({ myArray: [...this.state.myArray, ...[1,2,3] ] }) //another array
Spread syntax
Array push returns length
this.state.myArray.push('new value') returns the length of the extended array, instead of the array itself.Array.prototype.push().
I guess you expect the returned value to be the array.
Immutability
It seems it's rather the behaviour of React:
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.React.Component.
I guess, you would do it like this (not familiar with React):
var joined = this.state.myArray.concat('new value');
this.setState({ myArray: joined })
Functional Components & React Hooks
const [array,setArray] = useState([]);
Push value at the end:
setArray(oldArray => [...oldArray,newValue] );
Push value at the start:
setArray(oldArray => [newValue,...oldArray] );
Never recommended to mutate the state directly.
The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:
Push string to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, "new value"]
}))
Push string to beginning of the array
this.setState(prevState => ({
myArray: ["new value", ...prevState.myArray]
}))
Push object to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, {"name": "object"}]
}))
Push object to beginning of the array
this.setState(prevState => ({
myArray: [ {"name": "object"}, ...prevState.myArray]
}))
You should not be operating the state at all. At least, not directly. If you want to update your array, you'll want to do something like this.
var newStateArray = this.state.myArray.slice();
newStateArray.push('new value');
this.setState(myArray: newStateArray);
Working on the state object directly is not desirable. You can also take a look at React's immutability helpers.
https://facebook.github.io/react/docs/update.html
Here you can not push the object to a state array like this. You can push like your way in normal array.
Here you have to set the state,
this.setState({
myArray: [...this.state.myArray, 'new value']
})
You can use .concat method to create copy of your array with new data:
this.setState({ myArray: this.state.myArray.concat('new value') })
But beware of special behaviour of .concat method when passing arrays - [1, 2].concat(['foo', 3], 'bar') will result in [1, 2, 'foo', 3, 'bar'].
Using react hooks, you can do following way
const [countryList, setCountries] = useState([]);
setCountries((countryList) => [
...countryList,
"India",
]);
This Code work for me :
fetch('http://localhost:8080')
.then(response => response.json())
.then(json => {
this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
})
React-Native
if u also want ur UI (ie. ur flatList) to be up to date, use PrevState:
in the example below if user clicks on the button , it is going to add a new object to the list( both in the model and UI)
data: ['shopping','reading'] // declared in constructor
onPress={() => {this.setState((prevState, props) => {
return {data: [new obj].concat(prevState.data) };
})}}.
In the following way we can check and update the objects
this.setState(prevState => ({
Chart: this.state.Chart.length !== 0 ? [...prevState.Chart,data[data.length - 1]] : data
}));
setState([...prevState, {
label: newState.name,
value: newState.id
}]);
Was working with the dropdowns and wanted to implement this scenario there, i found this simple solution for dropdown with multiple values.
you are breaking React principles, you should clone the old state then merge it with the new data, you shouldn't manipulate your state directly,
your code should go like this
fetch('http://localhost:8080').then(response => response.json()).then(json ={this.setState({mystate[...this.state.mystate, json]}) })
If you use:
const[myArr, setMyArr] = useState([]);
for add:
setMyArr([...myArr, value]);
and for remove:
let index = myArr.indexOf(value);
if(index !== -1)
setPatch([...myArr.slice(0, index), ...myArr.slice(index, myArr.length-1)]);
I guess this is a little bit late for an answer but for those new to react
You can use this tiny package called immer
see this example: https://immerjs.github.io/immer/produce
I have a collection, where each single document have an array of references.
The structure in Firestore is like this:
collection1
doc1
doc2
doc3
name: string
...
refs: Array
collection2/x786123nd...
collection2/z1237683s...
...
I want to convert that into:
[
{<doc1 as object>},
{<doc2 as object},
{ name: "Document 3", refsResult:
[
{<object here mapped from reference>},
{<object here mapped from reference>},
]
}
]
Long story short, from firebase I need to get an output of a list of object where each object has a list of objects instead of references.
THE FOLLOWING SNIPPET IS NOT WORKING
I am trying to do it at service level in Angular using RxJS to transform the output, something like this:
return this.afs.collection('collection1')
.valueChanges()
.pipe(
switchMap(objects => from(objects).pipe(
switchMap(obj => from(obj.refs).pipe(
switchMap(ref => ref.get()),
map(r => ({ ...obj, refsResult: r.data() })),
tap(obj => console.log('OBJ', obj)),
)),
)),
tap(objects => console.log(objects))
);
But it seems that I only receive one object instead of a list of objects with 2. Also, it seems that the refsResult is also a single object instead of an array. I am sure I am using the switchMaps wrong, probably they are cancelling the others results or similar.
I would like to keep the solution within the chain of rxjs operators.
EDIT 1
I got it working in a hacky way, I am just sharing it here in order to give more context, and see if we can find a way to do it with RxJS.
return this.afs.collection('collection1')
.valueChanges()
.pipe(
tap(objects => {
objects.forEach(object => {
object.refsResult = [];
object.refs.forEach(ref => {
ref.get().then(d => object.refsResult.push(d.data()));
})
})
}),
tap(programs => console.log(programs))
);
The code above works but not very well, since first return the collection and then, mutates the objects to inject the items within the array. Hopefully it helps.
Help is very appreciated! :)
You can use combineLatest to create a single observable that emits an array of data.
In your case, we can use it twice in a nested way:
to handle each object in collection
to handle each ref in refs array for each object
The result is a single observable that emits the full collection of objects, each with their full collection of ref result data. You then only need a single
switchMap to handle subscribing to this single observable:
return this.afs.collection('collection1').valueChanges().pipe(
switchMap(objects => combineLatest(
objects.map(obj => combineLatest(
obj.refs.map(ref => from(ref.get()).pipe(map(r => r.data())))
).pipe(
map(refsResult => ({ ...obj, refsResult }))
)
))
))
);
For readability, I'd probably create a separate function:
function appendRefData(obj) {
return combineLatest(
obj.refs.map(ref => ref.get().pipe(map(r => r.data())))
).pipe(
map(refsResult => ({ ...obj, refsResult }))
);
}
return this.afs.collection('collection1').valueChanges().pipe(
switchMap(objects => combineLatest(objects.map(appendRefData)))
);
I just started learning about hooks, and according to the official docs on Using Multiple State Variables, we find the following line:
However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
So, if I understand correctly, this mean I don't need to use the spread operator for updating the state?
You still don't want to mutate state. So if your state is an object, you'll want to create a new object and set with that. This may involve spreading the old state. For example:
const [person, setPerson] = useState({ name: 'alice', age: 30 });
const onClick = () => {
// Do this:
setPerson(prevPerson => {
return {
...prevPerson,
age: prevPerson.age + 1
}
})
// Not this:
//setPerson(prevPerson => {
// prevPerson.age++;
// return prevPerson;
//});
}
That said, using hooks you often no longer need your state to be an object, and can instead use useState multiple times. If you're not using objects or arrays, then copying is not needed, so spreading is also not needed.
const [name, setName] = useState('alice');
const [age, setAge] = useState(30);
const onClick = () => {
setAge(prevAge => prevAge + 1);
}
What it means is that if you define a state variable like this:
const [myThings, changeMyThings] = useState({cats: 'yes', strings: 'yellow', pizza: true })
Then you do something like changeMyThings({ cats: 'no' }), the resulting state object will just be { cats: 'no' }. The new value is not merged into the old one, it is just replaced. If you want to maintain the whole state object, you would want to use the spread operator:
changeMyThings({ ...myThings, cats: 'no' })
This would give you your original state object and only update the one thing you changed.
I am trying to setState of a component from an array of values.
I have these values on filtersSelected array->
["name", "date", "state"]
I want to set these values to my state like this
myState = {
...etc,
name: null,
date: null,
state: null
}
I tried using
this.setState(previousState => ({
...previousState,
...filtersSelected: null
}))
apparently it doesn't work.Can anyone help me?
In order to spread the array into an object you first need to convert the array into an object and then you can spread the object keys into the state:
this.setState((prevState) => ({
...prevState,
...filtersSelected.reduce(function(acc, item) {
return Object.assign(acc, {[item]: null})
}, {});
}))
There are a couple things to note here. First of all, when you call setState, you do not need to provide all of the previous state. setState "merges" the specific state properties that you pass into the already existing state.
It also might be helpful to make your filters selected array an object, as you are trying to use the spread , but is by no means necessary. If you want to keep the array, you can use the code below.
let filtersSelected = ["name", "date", "state"];
this.setState({name: filtersSelected[0], date: filtersSelected[1], state: filtersSelected[2]});
Or, if you make filtersSelected into an object (which I highly recommend), you can do:
let filtersSelected = {name: "name", date: "date", state: "state"};
this.setState(filtersSelected);
Convert your array to object first then use a loop to assign values to your newly created object.
let filteredSelected = ["name", "date", "state"];
let obj;
for(let i of filteredSelected){
obj[i] = null;
}
this.setState(previousState => ({
...previousState,
obj
}))
Everyone has his own way, here are some ways I like
let data = ["name", "date", "state"];
Object.values(data).map(i => this.setState({ [i]: null }))
But I don't like to iterate setState for each element
let data = Object.assign({}, ...["name", "date", "state"].map((prop) => ({[prop]: null})))
this.setState(data)
Or you can do like so
this.setState(["name", "date", "state"].reduce( ( accumulator, currentValue ) => ({...accumulator,[ currentValue ]: null}), {} ));
sorry if the title isn't great but what I want to do is this:
componentDidMount() {
axios.get('http://thecatapi.com/api/images/get?format=xml&results_per_page=9')
.then((response) => {
parseString(response.data, (err, result) => {
result.response.data[0].images[0].image.map((cat) => this.setState({ cats: this.state.cats.concat(cat) }))
})
});
}
this function, pulls from catapi, then sets state of my cat state array to every cat object. each cat object has 3 keys from the cat api, how can I add an extra key called likes for every array object?
im thinking something like:
this.setState({ cats: this.state.cats.concat(cat), likes: 0 })
but obviously that doens't work
anyone got any ideas?
ah done it
just had to add it in like so:
result.response.data[0].images[0].image.map((cat) => (
cat.likes = 0,
this.setState({ cats: this.state.cats.concat(cat) }))
)
.map() returns an array which I believe means you are returning an array of items calling setState. Without seeing the overall logic I can't be sure exactly what you're doing but I would probably fetch the data, map it to a new array of objects, adding your key during the map, and lastly setState using the newly mapped array.