Updating state array using Spread operator in React? - reactjs

I am trying to update a state array after accepting the input from Form. State already has events object in it with data and I want to accept data from the form and append to the existing list. I'm able to do console log of the newEvents (data from form in another component), but using the spread operator unable to update the existing state by appending it.
//getting newEvent object from form in another component. // events is
an array in state which has data //priniting the existing array
without appending newEvent //printing data from form (newEvent)
properly
testmethodfornow = (newEvent) => (
{ ...this.state.events, events: [newEvent, ...this.state.events] },
console.log(this.state.events),
console.log(newEvent)
)
I want newEvent to be added to event :( Can anyone please help ?
NewEvent structure:
const newEvent = { IdText: uuid(), EventName, Venue, Time };
events array structure in state:
state = {
flag: false,
title: "State Check",
events: [
{
IdText: '1',
EventName: 'New Year Party 2022',
Venue: 'The Social',
Time: '8:00 PM- 1:00AM',
Date: ''
},
{
IdText: '2',
EventName: 'StandUp Comedy',
Venue: 'ShilpaRamam',
Time: '8:00 PM- 1:00AM',
Date: ''
}]
}

If you are using class components, use setState to update the state. In this case events is a field of the state. So you can spread rest of the state instead of state.events.
addNewEvent(){
setState({...this.state, events: [newEvent, ...this.state.events]})
}

Related

How do I select and update an object from a larger group of objects in Recoil?

My situation is the following:
I have an array of game objects stored as an atom, each game in the array is of the same type and structure.
I have another atom which allows me to store the id of a game in the array that has been "targeted".
I have a selector which I can use to get the targeted game object by searching the array for a match between the game ids and the targeted game id I have stored.
Elsewhere in the application the game is rendered as a DOM element and calculations are made which I want to use to update the data in the game object in the global state.
It's this last step that's throwing me off. Should my selector be writable so I can update the game object? How do I do this?
This is a rough outline of the code I have:
export const gamesAtom = atom<GameData[]>({
key: 'games',
default: [
{
id: 1,
name: 'Bingo',
difficulty: 'easy',
},
{
id: 21,
name: 'Yahtzee',
difficulty: 'moderate',
},
{
id: 3,
name: 'Twister',
difficulty: 'hard',
},
],
});
export const targetGameIdAtom = atom<number | null>({
key: 'targetGameId',
default: null,
});
export const targetGameSelector = selector<GameData | undefined>({
key: 'targetGame',
get: ({ get }) => {
return get(gamesAtom).find(
(game: GameData) => game.id === get(selectedGameIdAtom)
);
},
// This is where I'm getting tripped up. Is this the place to do this? What would I write in here?
set: ({ set, get }, newValue) => {},
});
// Elsewhere in the application the data for the targetGame is pulled down and new values are provided for it. For example, perhaps I want to change the difficulty of Twister to "extreme" by sending up the newValue of {...targetGame, difficulty: 'extreme'}
Any help or being pointed in the right direction will be appreciated. Thanks!

Adding observable object inside an array

I am having such a difficulty inserting observable into an array. What am I doing wrong here..
app.component.ts
const secondNavList = [];
this.appService.issuerList$.subscribe(iss => {
iss.forEach(value => {
console.log(value) //prints {name: 'A', id:'1'} {name: 'B', id:'2'}
secondNavList.push({
config: {
label: value.name
id: value.id
},
type: 'button'
});
});
};
console.log(secondNavList) // prints []
//But I want
//(2)[{...}.{...}]
appService.ts
get issuerList$(): Observable<Issuer[]>{
return this._issuerList.asObservable();
}
getIssuerList(){
const url = DBUrl
this.httpService.getData(url).subscribe((data:any[]) => {
let issuerList = [];
data.forEach(x=>{
issuerList.push(<Issuer>{name: x.issuerName, id: x.issuerId.toString()});
});
this._issuerList.next(issuerList)
})
}
Although inside my secondNavList, it contains data but I can't access it.
The fundamental issue you have is that you're trying to display the value of secondNavList before it is actually set in the subscriber. The rxjs streams are asynchronous, which implies that the the callback inside the subscribe method that appends to the list will get executed at some unknown point after subscribe is executed.
More importantly, I'd recommend that you try to take advantage of the map operator and array.map method, as well as the asyncronous pipes.
appService.ts
readonly issueUpdateSubject = new Subject<string>();
readonly issuerList$ = this.issueUpdateSubject.pipe(
switchMap(url => this.httpService.getData(url)),
map((data: any[]) => data.map(x => ({ name: x.issuerName, id: x.issuerId.toString() }))),
shareReplay(1)
);
getIssuerList() {
this.issueUpdateSubject.next(DBUrl);
}
app.component.ts
readonly secondNavList$ = this.appService.issuerList$.pipe(
map(iss => iss.map(value => ({
config: { label: value.name, id: value.id },
type: 'button'
}))
);
In the appService, instead of having an observable update a subject, I just had a subject emit update requests. Then instead of having to convert the subject to an observable, it just is an observable.
The shareReplay operator will share the most recently emitted list to any new subscribers.
Instead of appending to new arrays, I just use the array.map method to map each array element to the new desired object.
Instead of creating new array outside of the observable, and setting them in subscribe, I use the map operator to stream the latest instances of the arrays.
I find the more comfortable I got with rxjs the less I actually set the values of streams to instances of variables and rarely call subscribe - I just connect more and more streams and there values are used in components via async pipes. It's hard to get your head around it at first (or after a year) of using rxjs, but it's worth it in the end.
The error is because the observable value is an object array, and you want to add this into a simple object.
Try this.
const secondNavList = [];
this.appService.issuerList$.subscribe(iss => {
iss.forEach(value => {
console.log(value) //prints {name: 'A', id:'1'} {name: 'B', id:'2'}
value.forEach(v => {
secondNavList.push({
config: {
label: v.name,
id: v.id
},
type: 'button'
});
});
});
};
console.log(secondNavList) // prints []

adding a row to the react table by clicking a button

I am looking to add a new row to the existing table. Is there any way achieving that I have read the api of the reactdatagrid but no luck in finding it. any other way of doing this?
First map you need the data you need to render in table to your component state. Let's say to an array.
tableData = [
{
id: 1,
name: 'Tim',
age: 25,
},
{
id: 2,
name: 'Jane',
age: 22,
},
]
And populate the table with that state array. Then add a button and on button click just push some empty value to the array.
const { tableData } = this.state;
tableData.push({ id: null, name: '', age: null});
this.setState({ tableData });
Just add a new entry on your data that you feed on the table. So setState when you press a button to append the data.

What's the best way to work with a relational firestore model?

I followed this video on the best practices for creating flat databases with firestore: Converting SQL structures to Firebase structures
I came up with something that looks like this:
const firestore = {
events: {
eventID: { // Doc
description: "Event Description", // Field
title: "Event Title", // Field
}
},
eventComments: { // Collection
eventID: { // Doc
comments: { // Field
commentID1: true, // Value
commentID2: true, // Value
commentID3: true, // Value
}
}
},
comments: { // Collection
commentID1: { // Doc
createdAt: "Timestamp", // Field
createdBy: "uid", // Field
content: "Comment Body" // Field
},
commentID2: {...},
commentID3: {...},
},
};
I'm not sure what the best way to get the related data is however
I'm using react and react-redux-firestore to access the data. My current setup for the app looks like this
<EventsDetailPage>
<Comments>
<Comment />
<Comment />
<Comment />
</Comments>
</EventsDetailPage>
I've come up with two potential methods...
Method 1
I have useFirestoreConnect in each component. The top level gets the event and passes the eventID to the comments component, the comments component uses the eventID to get the eventComments list which passes the individual commentID for each comment to the comment component, then finally the individual comment component uses the commentID to get the relevant comment data.
My issue with this: Wouldn't this mean that there is a listener for the event, comment list, and every individual comment? Is that frowned upon?
EX: This would be in the event, the comments, and comment component but each with respective values
useFirestoreConnect(() => [
{collection: 'events', doc: eventID},
]);
const event = useSelector(({firestore: {data}}) => data.events && data.events[eventID]);
Method 2
Let's say I have a list of events, I can do a query to get the lists
useFirestoreConnect(() => [{
collection: 'events',
orderBy: ["createdAt", "desc"],
limitTo: 10
}]);
const events = useSelector(({ firestore: { ordered } }) => ordered.events);
This is great because I believe it's one listener but if any of the data is changed in any of the events the listener will still respond to the changes.
My issue with this: I don't know how to do a where clause that would return all events for a given list of IDs.
So like say if I wanted to get a list of events with where: ['id', '==', ['eventID1', 'eventID2', 'eventID3']]
To retrieve up to 10 items by their ID, you can use an in query:
.where('id', 'in', ['eventID1', 'eventID2', 'eventID3'])
If you have more than 10 IDs, you'll have to run multiple of these queries.

Understanding state in React

I am creating an app which displays and hides UI elements on the page based on checkbox value of a 'toggler' and checkbox values of the list elements which are created from this.state.items.
The app has an initial state set as:
state = {
items: [
{
id: 1,
name: 'Water',
isComplete: false
}, {
id: 2,
name: 'Salt',
isComplete: false
}, {
id: 3,
name: 'Bread',
isComplete: false
}
],
isChecked: false,
currentItem: '',
inputMessage: 'Add Items to Shopping Basket'
}
I created the following method which filters through items and returns all of the isComplete: false, and then I set the new state with these returned items.
toggleChange = (e) => {
this.setState({isChecked: !this.state.isChecked});
if (!this.state.isChecked) {
const filtered = this.state.items.filter(isComplete);
this.setState({items: filtered})
} else {
// display previous state of this.state.items
}
}
How do I come back to the 'Previous' state when I set 'toggler' to false?
If you only need to keep the default list then keep it out of state entirely
and just keep the filtered list in the state.
This way you could always filter the original list.
You can even consider filtering in the render method itself and not keeping the filtered list in state at all.
If you need to go back before a change was made (keeping history)
you could maintain another filtered list in your state.
I am not sure what you want to do but, if you implement componentWillUpdate() (https://facebook.github.io/react/docs/react-component.html#componentwillupdate) you get access to the next state and the current state.
-nextState will be what your state is going to be, and this.state is what it is now.
You could save this.state into another variable called this.oldState before it is updated, and then refer back to it.
Nonetheless, since state doesn't keep history of itself, you might consider approaching the problem differently.

Resources