how to update state when state is object that contains array of objects in React 17 - react-hooks

I have a form that collects demographic information for one adult and also for each child (minor) that will accompany the adult. Example of data for one minor: {firstName: "Jimmy", lastName: "Smith"}).
I can use setMinors((prevArray) => [...prevArray, data]) to update the minors state as the user adds, edits and possibly deletes minors.
On form submit, I want to setWaiver() with the collection of objects from the minors state at that time, but I cannot determine the correct way to do this.
const[minors, setMinors] = useState([]);
const[waiver, setWaiver] = useState({ firstName : "", lastName : "", email : "", minors: [] });
If you have insight into how to accomplish updating the waiver state to include the objects from the minors state, I appreciate your help.

If you want to update minors in waiver that code will help you
const[minors, setMinors] = useState([]);
const[waiver, setWaiver] = useState({ firstName : "", lastName : "", email : "", minors: [] });
const updateMinors=(newMinors)=>{
setWaiver((prevWaiver)=>({...prevWaiver, minors : newMinors}));
}

Related

Mailchimp API Node - create campaign for list based on tags

I'm making an async api request with a firebase cloud function to create a campaign within mailchimp for a specific set of users from a list. I read in the documentation that this can be done with tags this way I can build my own structure. I'm building a donation system for a nonprofit and would like the tag to represent the name of a client who is currently being donated to.
Below is my firebase function. I'm stuck at the segment_opts object. I want to define a segment based on whether the list member has a tag equivalent my clients name.
The doc says segment_opts is "An object representing all segmentation options. This object should contain a saved_segment_id to use an existing segment, or you can create a new segment by including both match and conditions options.". I don't have any other segments set up so I figured I'd create a new one that specifies the tags to contain the client's name.
This post helped me get to this point. Stackoverflow post
I now see that condition is supposed to be a Segment Type but in the dropdown I don't see an option for Tags. Here is a link to the documentation reference. Reference
const response = await mailchimp.post('/campaigns', {
type: 'regular',
recipients: {
list_id: functions.config().mailchimp.test,
segment_opts: {
"match": "any",
"conditions": match: 'any',
conditions: [
{
condition_type: 'StaticSegment',
field: 'static_segment',
op: 'static_is',
value: ??? (Int),
},
],
}
},
});
For now I removed segment_opts and will settle on sending campaign to entire list until I figure out how to segment by tags. This version works and creates a campaign within my mailchimp account and from the UI I can see the segment options offered in the documentation but don't see an option to filter by tags
const response = await mailchimp.post('/campaigns', {
type: 'regular',
recipients: {
list_id: functions.config().mailchimp.test,
},
settings: {
subject_line: `${firstName} has been funded!`,
preview_text: `$${goal} has been raised for ${firstName}.`,
title: `${firstName} has been funded`,
from_name: 'Organization name',
reply_to: 'org_email#gmail.com',
},
});
Here is a screenshot of the dropdown options in Mailchimp dashboard.
This is what I have for my campaign segment options. Here I'm checking for two conditions. Is the SITE merge tag = the site variable I pass in, and also does the member belong to the tag/segment called tagName. However, I can't pass a tagName, only a tagId which I lookup beforehand.
'segment_opts':
{
'match': 'all',
'conditions': [
{
'condition_type': 'TextMerge',
'field': 'SITE',
'op': 'is',
'value': site
},
{
'condition_type': 'StaticSegment',
'field': 'static_segment',
'op': 'static_is',
'value': tagId
}
]
}
To get the tagId I use this Python function:
tagId, segments = self.getSegmentIdFromTagName(tagName)
This is the Python code to get the tagId from the tagName, which gets all the Segments/Tags from the system and then looks for the name you pass in:
def getSegmentIdFromTagName(self,reqTagName,segments=None):
audienceId = self.audienceId
reqId = None
if not segments:
segments = self.mcClient.lists.segments.all(list_id=audienceId,get_all=True)
for segment in segments['segments']:
segName = segment['name']
segId = segment['id']
if segName == reqTagName:
reqId = segId
break
return reqId,segments

Wait for Subscription set Recursively to Complete

I have an array of objects with children and have a need to set a field (hidden) in each of those objects recursively. The value for each is set in a subscription. I want to wait until each item in the array is recursively updated before the subscription is complete.
The hidden field will be set based on roles and permissions derived from another observable. In the example I added a delay to simulate that.
Here's my first pass at it. I'm certain there is a much cleaner way of going about this.
https://codesandbox.io/s/rxjs-playground-hp3wr
// Array structure. Note children.
const navigation = [
{
id: "applications",
title: "Applications",
children: [
{
id: "dashboard",
title: "Dashboard"
},
{
id: "clients",
title: "Clients"
},
{
id: "documents",
title: "Documents",
children: [
{
id: "dashboard",
title: "Dashboard"
},...
]
},
{
id: "reports",
title: "Reports"
},
{
id: "resources",
title: "Resources"
}
]
}
];
In the code sandbox example, looking at the console messages, I get the correct result. However, I would like to avoid having to subscribe in setHidden and recursivelySetHidden. I would also like to avoid using Subject if possible.
Here is my approach:
const roleObservable = timer(1000).pipe(mapTo("**************"));
function populateWithField(o, field, fieldValue) {
if (Array.isArray(o)) {
return from(o).pipe(
concatMap(c => populateWithField(c, field, fieldValue)),
toArray()
);
}
if (o.children) {
return roleObservable.pipe(
tap(role => (fieldValue = role)),
concatMap(role => populateWithField(o.children, field, role)),
map(children => ({
...o,
[field]: fieldValue,
children
}))
);
}
return roleObservable.pipe(
map(role => ({
[field]: role,
...o
}))
);
}
of(navigation)
.pipe(concatMap(o => populateWithField(o, "hidden")))
.subscribe(console.log, e => console.error(e.message));
The main thing to notice is the frequent use of concatMap. It it a higher-order mapping operator which means, among other things, that it will automatically subscribe to/unsubscribe from its inner observable.
What differentiates concatMap from other operators, is that it keeps a buffer of emitted values, which means that it will wait for the current inner observable to complete before subscribing to the next one.
In this case, you'd have to deal with a lot of Observables-of-Observables(higher-order observables), which is why you have to use concatMap every time you encounter a children property. Any child in that property could have their own children property, so you must make sure an Observable contains only first-order Observables.
You can read more about higher-order and first-order observables here.
Here is a CodeSandbox example

RxJS: How many events is too many for combineLatest?

I've got a toolbar where each tool can be disabled/hidden independently. The tools depend on other system events and emit individual events configuring their availability. The toolbar uses combineLatest to pull all the tools together and emit a toolbar config.
The combineLatest is listening to 40+ events.
Will this be a performance problem? Is there a practical limit to how many events combineLatest can consume?
Hard to say just like that.
I think that having a huge number of streams combined is not a problem on it's own.
What could be:
- having those streams emitting a value very very often
- having those streams triggering Angular change detection (might be worth running them outside ng zone if possible)
That said, I think that the performance problem here is hiding a conception problem eventually. It really feels like you might need a "single source of truth". Having a look into Redux and eventually Ngrx might be a great help for you.
Then from the unique store, you could easily retrieve the availability of your tools.
The tools depend on other system events and emit individual events
The Redux pattern is generally playing very well with that kind of challenges:
- Async
- State
It really sounds like it might be a perfect fit here.
If you don't know where to start, I'd advise you to first read the Redux documentation. It's one of the best I've ever read: https://redux.js.org
Once you understand how Redux works and whether it's a good fit for you or not, if the answer is yes then take a look into Ngrx. As you seem to be working with streams a lot, if you take the time to learn Redux first then Ngrx will definitely not be a problem: https://redux.js.org
If you decide to go this way, good luck into this amazing journey of reactive and functional programming :)
EDIT 11/07:
If you think that Redux is overkill then maybe you could build a minimal solution that acts a bit like it. The following is completely type safe and you can update multiple properties without firing the final stream as many times as you update properties. Once is enough:
import { BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';
type YourDataType = {
prop1: string,
prop2: string,
prop3: string,
prop4: string,
// ...
prop40: string,
};
const properties$: BehaviorSubject<YourDataType> = new BehaviorSubject({
prop1: '',
prop2: '',
prop3: '',
prop4: '',
// ...
prop40: '',
});
const patchProperties = (updatedProperties: Partial<YourDataType>) =>
properties$.next({
...properties$.getValue(),
...updatedProperties
});
properties$
.pipe(
tap(x => console.log(JSON.stringify(x, null, 2)))
)
.subscribe()
patchProperties({
prop3: 'new prop 3'
});
patchProperties({
prop1: 'new prop 1',
prop2: 'new prop 2',
prop3: 'final prop 3',
prop40: 'new prop 40',
});
Produces the following output:
{
"prop1": "",
"prop2": "",
"prop3": "",
"prop4": "",
"prop40": ""
}
{
"prop1": "",
"prop2": "",
"prop3": "new prop 3",
"prop4": "",
"prop40": ""
}
{
"prop1": "new prop 1",
"prop2": "new prop 2",
"prop3": "final prop 3",
"prop4": "",
"prop40": "new prop 40"
}
Here's a Stackblitz demo:
https://stackblitz.com/edit/typescript-zafsnk?file=index.ts

RethinkDB: JOIN with dynamic property/field names

The solution to this RethinkDB query has eluded me for days now. Does not help that I'm a t-SQL developer...
Given a document structure like this:
var messages = {
"Category A" : {
catName: 'Category A',
userInfo: {
userName: 'Hoot',
userId: 12345,
count: 77
}
},
"Category FOO" : {
catName: 'Category FOO',
userInfo: {
userName: 'Woot',
userId: 67890,
count: 42
}
}
}
where "Category A" and "Category FOO" could be any string entered by a user in the app, basically a dynamic property. This is great for iterating on client side JS using square bracket notation. (messages["Category A"]).
I need to update, using ReQL, the userInfo.count field by "joining" to another table UserMessages on userId field, which exists in both tables.
I've managed to add the result (e.g: "12345":77, "67890": 42 ) as a subdocument to "messages" object using r.forEach() and r.object().
But I must have the result added to the userInfo.
EDIT: To add clarity... I struggle to navigate to the userId if I don't know the Top-level property. ie.:
r.table('messages')('???????????')('userInfo')('userId')
Any suggestions would be greatly appreciated.
Thank you.

is there a way to define a Model in mongoose that does not have the .save() method?

what i'm trying to do is define a Student model with a field called "schedules" which contains an Array of Schedule instances, but i don't want the Schedule model to be able to saved to it's own collection.
Here's some code, it'll make more sense:
var ScheduleSchema = new Schema({
day: {type: Number, min: 0, max: 6},
time: {type: Number, min: 0, max:24}
});
var StudentSchema = new Schema({
firstName: String,
schedule: [ScheduleSchema]
});
var Schedule = mongoose.model("Schedule", ScheduleSchema);
var Student = mongoose.model(modelName, StudentSchema);
Student.Schedule = Schedule;
the problem i'm having with this bit of code is that when i do:
var schedule = new Student.Schedule({day: 3, time: 15});
i would get something like this, when i console.log
{ "day" : 3, "time" : 15, "_id" : ObjectId("5019f34924ee03e20900001a") }
i got around the auto generating of _id, by explicitly defining _id in the schema,
var ScheduleSchema = new Schema({
_id: ObjectIdSchema,
day: {type: Number, min: 0, max: 6},
time: {type: Number, min: 0, max:24}
});
now it' just gives me:
{ "day" : 3, "time" : 15}
that's probably a hack..and not something i want to rely on.
the other problem is that if i do
schedule.save()
it'll actually create a collection and save the document to the database.
is there a way to disable save() for Schedule? is there a correct way to do this?
i could probably stick with what i have, or settle for Mixed types but lose out on the validation..
Why would you do schedule.save() anyway? You are supposed to save the parent object (i.e. Student object), not embedded document. If you save schedule then the _id will be appended by default. This won't happen if you save Student object with schedule as an embedded document.
Also there is no real reason for disabling .save method on your Schedule model (this is an interesting feature that allows you to have Schedule model as a model for embedded documents and a model for stand-alone documents at the same time). Just don't use it. Do something like this:
var student = new Student({ firstName: 'John' });
var schedule = new Schedule({ day: 3, time: 15 });
student.schedules.push( schedule );
student.save( );

Resources