How do I partially update a nested schema in mongoose? - javascript

EDIT:
After trying many different approaches i found a working solution that maps any object to a format that mongoose understands. See solution here: https://stackoverflow.com/a/69547021/17426304
const updateNestedObjectParser = (nestedUpdateObject) => {
const final = {
}
Object.keys(nestedUpdateObject).forEach(k => {
if (typeof nestedUpdateObject[k] === 'object' && !Array.isArray(nestedUpdateObject[k])) {
const res = updateNestedObjectParser(nestedUpdateObject[k])
Object.keys(res).forEach(a => {
final[`${k}.${a}`] = res[a]
})
}
else
final[k] = nestedUpdateObject[k]
})
return final
}
ORIGINAL QUESTION:
I have a mongoose structure of
ChildSchema = {
childProperty: String,
childProperty2: String
}
MainSchema = {
mainProperty: String,
mainProperty2: String,
child: childSchema
}
In my update function I want to pass a partial object of mainSchema and only update the properties I pass to the function.
This works fine for direct properties on my mainSchema but not on my childSchema. It overwrites the whole child property with the partial object given by my request.
So my update object looks something like this
const updates = {
child: {
childProperty2: 'Example2'
}
}
How can I only update the childProperty2 without deleting the childProperty?
In this example it would be easy to just update every property alone but the real world objects are much bigger and can be nested into multiple levels.
I tried to use destructuring but it does not seem to work
const example = MainSchema.findOne({_id})
if (updates.child) example.child = {...example.child, ...updates.child} // Does not work
Is there a solution to that in mongoose (6.0)?

Change your code like this:
const updates = {
"child.childProperty2": 'Example2'
}

Related

How do i return an object from my react state

I am trying to find an item from a collection, from the code below, in order to update my react component, the propertState object isnt empty, it contains a list which i have console logged, however I seem to get an underfined object when i console log the value returned from my findProperty function... I am trying update my localState with that value so that my component can render the right data.
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState()
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
return (<>{property.propertyName}</>)
}
export default PropertyComponent
There are a few things that you shall consider when you are finding data and updating states based on external sources of data --useParams--
I will try to explain the solution by dividing your code in small pieces
const PropertyComponent = () => {
const { propertyId } = useParams();
Piece A: Consider that useParams is a hook connected to the router, that means that you component might be reactive and will change every time that a param changes in the URL. Your param might be undefined or an string depending if the param is present in your URL
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
Piece B: useSelector is other property that will make your component reactive to changes related to that selector. Your selector might return undefined or something based on your selection logic.
const[property, setProperty] = useState()
Piece C: Your state that starts as undefined in the first render.
So far we have just discovered 3 pieces of code that might start as undefined or not.
const findProperty = (propertyId, properties) => {
let propertyReturn;
for (var i=0; i < properties.length; i++) {
if (properties[i].propertyId === propertyId) {
propertyToReturn = properties[i];
break;
}
}
setProperty(propertyReturn)
return propertyReturn;
}
const foundProperty = findProperty(propertyId, propertyState.properties);
Piece D: Here is where more problems start appearing, you are telling your code that in every render a function findProperty will be created and inside of it you are calling the setter of your state --setProperty--, generating an internal dependency.
I would suggest to think about the actions that you want to do in simple steps and then you can understand where each piece of code belongs to where.
Let's subdivide this last piece of code --Piece D-- but in steps, you want to:
Find something.
The find should happen if you have an array where to find and a property.
With the result I want to notify my component that something was found.
Step 1 and 2 can happen in a function defined outside of your component:
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
NOTE: I took the liberty of modify your code by simplifying a little
bit your find function.
Now we need to do the most important step, make your component react at the right time
const findProperty = (propertyId, properties) => properties.find((property) => property.propertyId === propertyId)
const PropertyComponent = () => {
const { propertyId } = useParams();
const propertyState: IPropertiesState = useSelector(
propertiesStateSelector
);
const[property, setProperty] = useState({ propertyName: '' }); // I suggest to add default values to have more predictable returns in your component
/**
* Here is where the magic begins and we try to mix all of our values in a consistent way (thinking on the previous pieces and the potential "undefined" values) We need to tell react "do something when the data is ready", for that reason we will use an effect
*/
useEffect(() => {
// This effect will run every time that the dependencies --second argument-- changes, then you react afterwards.
if(propertyId, propertyState.properties) {
const propertyFound = findProperty(propertyId, propertyState.properties);
if(propertyFound){ // Only if we have a result we will update our state.
setProperty(propertyFound);
}
}
}, [propertyId, propertyState.properties])
return (<>{property.propertyName}</>)
}
export default PropertyComponent
I think that in this way your intention might be more direct, but for sure there are other ways to do this. Depending of your intentions your code should be different, for instance I have a question:
What is it the purpose of this component? If its just for getting the property you could do a derived state, a little bit more complex selector. E.G.
function propertySelectorById(id) {
return function(store) {
const allProperties = propertiesStateSelector(store);
const foundProperty = findProperty(id, allProperties);
if( foundProperty ) {
return foundProperty;
} else {
return null; // Or empty object, up to you
}
}
}
Then you can use it in any component that uses the useParam, or just create a simple hook. E.G.
function usePropertySelectorHook() {
const { propertyId } = useParams();
const property = useSelector(propertySelectorById(propertyId));
return property;
}
And afterwards you can use this in any component
functon AnyComponent() {
const property = usePropertySelectorHook();
return <div> Magic {property}</div>
}
NOTE: I didn't test all the code, I wrote it directly in the comment but I think that should work.
Like this I think that there are even more ways to solve this, but its enough for now, hope that this helped you.
do you try this:
const found = propertyState.properties.find(element => element.propertyId === propertyId);
setProperty(found);
instead of all function findProperty

Why does mutating one property (Object) also change in a different and separately declared property?

I have 2 properties declared like so:
ngOnInit() {
this.defaultRequirements = myData.property1.countryDocument; //should never change
this.filteredRequirements = myData.property1.countryDocument;
}
When I run this onFilter function, the defaultRequirements property also gets mutated.
onFilter(selectedSections) {
let index = -1;
this.defaultRequirements.forEach(country => {
index++;
const filteredSectionsList = [];
country.section.forEach(section => {
selectedSections.value.forEach(selectedSelection => {
const selection = selectedSelection.split(/\s*[-]\s*/);
if (country.countryCode === selection[0]) {
console.log('matched');
filteredSectionsList.push(section);
}
});
const countryObj = Object.assign({}, country, {
section: [...filteredSectionsList]
})
// Here is the issue, when filtering, this.defaultRequirements also gets changed!
this.filteredRequirements[index] = countryObj;
})
})
}
The Problem
I don't understand how mutating this.filteredRequirements is ALSO mutating this.defaultRequirements (they both equal the same thing)! How can I avoid this behaviour and have defaultRequirements be unaffected by changes done on filteredRequirements?
Okay, so your declared object myData.property1.countryDocument is a non-primitive/reference value. So that means that both this.defaultRequirements and this.filteredRequirements are pointing to literally the same piece of data.
If you were to do this with a primitive value (eg: a string), you would get a copy; so this.defaultRequirements and this.filteredRequirements would be completely separate and could be manipulated with no effect on each other.
To duplicate/copy an object in the way that you intended is totally possible and there's been a lot of discussion about it already which I won't duplicate; I'd suggest you take a look at this which covers it nicely.
Try this:
ngOnInit() {
this.defaultRequirements = JSON.parse(JSON.stringify(myData.property1.countryDocument));
this.filteredRequirements = JSON.parse(JSON.stringify(myData.property1.countryDocument));
}
or
ngOnInit() {
this.defaultRequirements = {...myData.property1.countryDocument}
this.filteredRequirements = {...myData.property1.countryDocument}
}

Assign same value to different keys in object literal reactjs

i am using object literal as an alternative to if else/switch statements. In doing so not knowing how to assign same value to different keys.
What i am trying to do?
Based on variable named "user" should redirect to different links.
if value of "user" is admin or manager should redirect to say "www.exampledomain.com". If value of "user" is "purchaser" should redirect to "https://stackoverflow.com".
To do so i have used object literal instead of if-else which is clumsy.
Below is the code,
get user() {
return ( {
'admin': 'www.exampledomain.com',
'manager': 'www.exampledomain.com',
'purchaser': 'https://stackoverflow.com',
} )[user];}
As you see from above code, admin and manager keys point to same url "www.exampledomain.com". Is there a way to assign it something like below.
get user() {
return ( {
'admin': 'manager': www.exampledomain.com',
'purchaser': 'https://stackoverflow.com',
} )[user];}
Could somebody help me solving this. Thanks.
Personally, I don't see any reason to use the second idea, furthermoe it is not valid JS code.
If you are trying to reduce code duplication you can just extract your urls into a constant variable like that.
static get REDIRECT_URLS() {
return {
"PRIMARY_SITE" : "www.exampledomain.com",
"SECONDARY_SITE" : "stackoverflow.com",
};
}
get user() {
return ( {
'manager' : FooClass.REDIRECT_URLS.PRIMARY_SITE,
'admin': FooClass.REDIRECT_URLS.PRIMARY_SITE,
'purchaser': FooClass.REDIRECT_URLS.SECONDARY_SITE,
} )[user];}
Of course there are other possible solutions, like having keys like 'admin|manager' : "url", but that doesn't seem to be a good choice and you need to add extra logic to iterate over the object keys and check if a key matched the regex.
If the problem were to be viewed in isolation, I would solve this issue by simply flipping the data structure around
const linkToTypeMapping = {
'www.exampledomain.com': ['admin', 'manager'],
'https://stackoverflow.com': ['purchaser'],
}
But that doesn't really fix your issue.
The way I would solve the actual use-case is to simply add a link property to your user object and just populate and later access userObject.link.
However for completeness's sake, here's how you would extract a user's link from the data structure I posted above.
const get_link = (type) => {
for (let key in linkToTypeMapping) {
if(linkToTypeMapping.includes(type)) {
return key;
}
}
}
This is obviously very complicated as far as the code goes, but if your linkToTypeMapping object is expected to become fairly large, this might actually be the right solution for you.
You can make default route like that:
function user() {
const defaultRoute = "www.exampledomain.com"
return ({
'admin': defaultRoute,
'manager': defaultRoute,
'purchaser': 'https://stackoverflow.com'
})[user];
}
or that
function user2() {
const defaultRoute = "www.exampledomain.com"
return ({
'someoneElse': "www.google.com",
'purchaser': 'https://stackoverflow.com'
})[user] || defaultRoute;
}
or mix both styles, depending on how complex your statement is;
or even make it from the other side
function user3(user) {
const routes = {
"www.google.com": ["admin", "manager"],
"www.exampledomain.com": ["purchaser"]
};
return Object.keys(routes).find(k => routes[k].includes(user));
}
console.log(user3('manager')); //www.google.com

Angular - Create property in new property of object

Currently, I have a select element in my html which has a ngModel to the object details:
[ngModel]="details?.publicInformation?.firstname"
However, publicInformation may not exist in that object, or if it does, maybe firstname does not exist. No matter the case, in the end, I want to create the following:
[ngModel]="details?.publicInformation?.firstname" (ngModelChange)="details['publicInformation']['firstname'] = $event"
Basically, if the select is triggered, even if neither of publicInformation nor firstname exist, I would like to create them inside details and store the value from the select.
The issue is that I am getting
Cannot set property 'firstname' of undefined
Can someone explain what I am doing wrong here and how can I achieve the result I desire?
You need to initialize details and publicInformation to empty object
public details = {publicInformation : {}};
You should do that when you load the form data.
For example, you might have something like this:
ngOnInit() {
this._someService.loadForm().then((formData: FormData) => {
this.details = formData;
});
}
Then, you could modify that to fill in the missing empty properties you need:
ngOnInit() {
this._someService.loadForm().then((formData: FormData) => {
this.details = formData || {};
if (!this.details.publicInformation) {
this.details.publicInformation = { firstname: '' };
} else if (!this.details.publicInformation.firstname) {
this.details.publicInformation.firstname = '';
}
});
}
However, it would be better to place this logic in the services, so that they are responsible for adding all the necessary empty properties to the data they load, or if you are using Redux, then it should go into the reducers.

How to pass by value and not by reference in React?

I have the following file, LookupPage.jsx and AccountDetails.jsx.
In LookUp
this.updateCustomer = (customer) => {
if(JSON.stringify(customer.address) !== JSON.stringify(this.state.activeAccount.customer.address)) {
console.log('address changed');
customer.update_address = true;
customer.address.source = 'user';
}
return fetch(
`${API_ENDPOINT}/customer/${customer.id}/`,
{
method: 'PATCH',
headers: {
'Authorization': 'Token ' + this.props.session_token,
'Content-Type': 'application/json',
},
body: JSON.stringify(customer),
}
).then(restJSONResponseToPromise).then(responseJSON => {
if(responseJSON.results){
console.log('update customers client side.')
}
}, clearSessionIfInvalidToken(this.props.clearSession));
};
<AccountsDetailModal
show={this.state.showAccountDetail}
close={this.toggleAccountDetail}
customer={this.state.activeAccount.customer}
updateCustomer={this.updateCustomer}
/>
In side AccountDetails
this.onChangeAddress = (e) => {
const customer = {...this.state.customer};
const address = customer.address;
address[e.target.name] = e.target.value;
customer.address = address;
this.setState({customer, errors: {
...this.state.errors,
[e.target.name]: [],
}});
};
this.saveCustomer = () => {
this.setState({postDisable: true});
const errors = this.getFormErrors();
const hasErrors = !every(errors, (item) => !item.length);
if(!hasErrors){
this.props.updateCustomer(this.state.customer);
} else {
sweetAlert('Error!', 'Form is invalid.', 'error');
}
this.setState({postDisable: false});
};
this.componentDidMount = () => {
this.setState({customer: this.props.customer});
}
When I am updating the customers address, it is updating active accounts address, so it seems like it is being passed by reference. What I want to happen is only update the customer address if the address was changed/different from the original. How would I modify my code to do this?
You can pass any object by value in JS (whether you're using React or not) by passing:
JSON.parse(JSON.stringify(myObject))
as an argument instead of the object itself.
Essentially this will just clone the object and pass a copy of it, so you can manipulate the copy all you want without affecting the original.
Note that this will not work if the object contains functions, it will only copy the properties. (In your example this should be fine.)
I am going to put my two cents here:
First of all, this isn't really specific to React and is more of a JS related question.
Secondly, setting props against internal state is considered to be a bad practice when it comes to react. There's really no need to do that given your particular scenario. I am referring to
this.setState({customer: this.props.customer});
So, coming to your problem, the reason you are having reference issues is because you are mutating the original passed in object at certain points in your code. For instance, if I look at:
this.updateCustomer = (customer) => {
if(JSON.stringify(customer.address) !== JSON.stringify(this.state.activeAccount.customer.address)) {
console.log('address changed');
customer.update_address = true;
customer.address.source = 'user';
}
};
You are mutating the original props of the argument object which is very likely to be passed around in other methods of your component. So, to overcome that you can do:
const updatedCustomer = Object.assign({}, customer, {
update_address: true
});
And you can pass in updatedCustomer in your API call. Object.assign() will not perform operation on the passed in object but will return a new object so you can be sure that at any point in your app you are not mutating the original object.
Note: Object.assign would work on plain object and not a nested one. So, if you want to achieve something similar that would work on nested object properties too, you can use lodash merge.

Categories