append to state array and add extra object key - javascript

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.

Related

Why these HTML elements added to DOM using react/nextjs are not visible? [duplicate]

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

Firestore: Array of references within a document into an object with array of objects (AngularFire)

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

Pushing to an array is returning index rather then items (React)

I'm trying to add a new object to the end of an array that I'm dynamically fetching over my API. Users complete a form so the data is passed from the form to the state.
The initial first fetch is storing the original array to some react state which is fine, then at some point, a single object should be added to the end of the original array, so the whole array will contain the original data, plus the new object added.
Naturally, I'm trying to use array.push() to try and achieve this, but I keep getting the index rather than the data object.
// declare state will be an array
const [originalPages, setOriginalPages] = useState([]);
// Get all the existing data
loadInitialValues() {
return fetch(`example.com/api/collections/get/testing_features?token=${process.env.REACT_APP_API_KEY}`)
.then((res) => {
return res.json();
})
.then((res)=>{
// set state to be the whole array for this post
setOriginalPages(res.entries[4].pagebuild);
return res.entries[4].pagebuild[0].settings;
})
.catch((err)=>{
console.error(err);
})
}
At this point the data is all working completely fine, the collection of the new data from the forms works fine too. However, this is the point when the data goes to get posted back to update the API but just returns a number:
onSubmit(formData) {
// Dynamically hook all the newly collected form data to this new data object
let theData = {
component: 'page',
settings: {
title: formData.title,
pageOptions: {
pageSideImg: formData.pageOptions.pageSideImg,
isReversed: formData.pageOptions.isReversed,
paraGap: formData.pageOptions.paraGap,
paraFont: formData.pageOptions.paraFont,
},
pageNavigation: {
pageSlug: formData.pageNavigation.pageSlug,
nextPage: formData.pageNavigation.nextPage,
prevPage: formData.pageNavigation.prevPage,
},
globalOptions: {
projectName: formData.globalOptions.projectName,
transType: formData.globalOptions.transType,
menuTrans: formData.globalOptions.menuTrans,
},
blocks: formData.blocks
}
};
cms.alerts.info('Saving Content...');
return fetch(`example.com/api/collections/save/testing_features?token=${process.env.REACT_APP_API_KEY}`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: {
// Only returning the array count as a number
pagebuild: originalPages.push(theData),
_id: "610963c235316625d1000023"
}
})
})
.then(response => response.json())
.catch((err) => {
cms.alerts.error('Error Saving Content');
console.log(err);
});
},
If anyone has any ideas as to why this is happening id greatly appreciate it!
For reference, I've checked here and maybe I should use spread instead?
The Array.push doesn't return the final array you would need, but the final length of modified array (that's why you thought it was an index).
Replace this string pagebuild: originalPages.push(theData), with this one:
pagebuild: [...originalPages, theData],
Of course, if you want to update the internal originalPages state value, call this within your onSubmit():
setOriginalPages(x => [...x, theData]);

How to remove one item from an array using filter() in ReactJS - function not working properly

I have a function to delete a recipe object from an array of recipes objects.
I am actually able to delete a recipe (remove it from the array called recipeList), but when I hit one delete button, it removes ALL of the recipes in the array. I only want to remove the one that I am clicking on.
I am pretty sure it has something to do with this.state.id. It's coming up undefined when I console.log it. It should be referring to the key/id of the recipe being deleted.
Anyone have any ideas what the issue could be?
This is the delete function:
handleDeleteRecipe = recipeId => {
// copy current list of items
const recipeList = [...this.state.recipeList];
//filter out item being deleted
const filteredList = recipeList.filter(recipe => this.state.id !== recipeId);
this.setState({recipeList: filteredList});
console.log('id: ' + this.state.id);
}
In the initial state, id is inside of an object called newRecipe and it is set to empty:
this.state = {
recipeList: [],
newRecipe: {
title: '',
source: '',
servings: '',
id: ''
}
I have an add new recipe function where id is set to a random number between 1 and 1000. No idea if that would have anything to do with it. It is inside of the newRecipe object which then gets added to recipeList array.
handleAddNewRecipe = (title, source, servings, id) => {
const newRecipe = {
title: title,
source: source,
servings: servings,
id: (Math.floor(Math.random()* 1000))
This is where each recipe in the array gets mapped and returned as a Component. It has a key which is set to recipe.id.
{this.state.recipeList.map((recipe) => (
<RecipeCardThumbnail
title={recipe.title}
servings={recipe.servings}
key={recipe.id}
handleDeleteRecipe={this.handleDeleteRecipe}
/>
))}
This is the delete button from the child component:
onClick={() => this.props.handleDeleteRecipe(this.props.id)}>Delete</button>
There's obviously something I am not understanding but I am not sure what. Any help would be much appreciated!
EDIT: I also tried console.logging recipeList when I add my recipe to the array and the id showed up just fine. So I tried recipeList.id and the same issue occurred of all of the recipes getting removed.
EDIT AGAIN: I did an experiment and got rid of everything in the delete function except for the console.log. I tried logging recipeList and everything in the object was there. But when I logged recipeList.id or even recipeList.title they both showed up as undefined. I am still not sure what I am doing wrong though...
this.state.id will return undefined because you don't have id directly under your state.
try this
handleDeleteRecipe = recipeId => {
// copy current list of items
const recipeList = [...this.state.recipeList];
//filter out item being deleted
const filteredList = recipeList.filter(recipe => recipe.id !== recipeId);
this.setState({recipeList: filteredList});
Your filter looks wrong you are never using the recipe object that you are filtering on. I would assume it should be recipeList.filter(recipe => recipe.id !== recipeId)
Also, you do not need to copy the array first... Array.filter will return a new array anyway

Correct way to push into state array

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

Categories

Resources