JS copy one object to another - reactjs

I have the following object:
myModel =
{
Id: '',
Category :'',
Values: {
userName: '',
pasword: '',
address: ''
}
}
newModel may look like this:
{
Version : "12.1",
Values : {
"somenewProp" : "with a value"
}
I want myModel to look like this at the end of this merge:
myModel =
{
Id: '',
Category :'',
Version : "12.1",
Values: {
userName: '',
pasword: '',
address: '',
"somenewProp" : "with a value"
}
}
I have the exact same object format with values that I want to merge, I am doing the following:
this.myModel = Object.assign(this.myModel, ...newModel);
MyModel doesnt change, I need to be able to merge new properties and assign properties from the newModel into myModel

Object.assign is not recursive, nothing in the ECMAScript standard library offers recursive merging of two objects. You need to either :
Write your own recursive merge function
Use a third party library (eg. merge, lodash.merge, etc..)

Assume;
myModel = {
Id: '',
Category :'',
Values: {
userName: '',
pasword: '',
address: ''
}
}
subModel = {
Version : "12.1",
Values : {
"somenewProp" : "with a value"
}
}
Then newModel = {...myModel, Version: subModel.Version, Values: {...subModel.Values, ...myModel.Values}} will give you the result you want. Although, the readability of the code would be poor.

Related

update one element of array inside object and return immutable state - redux [duplicate]

In React's this.state I have a property called formErrors containing the following dynamic array of objects.
[
{fieldName: 'title', valid: false},
{fieldName: 'description', valid: true},
{fieldName: 'cityId', valid: false},
{fieldName: 'hostDescription', valid: false},
]
Let's say I would need to update state's object having the fieldName cityId to the valid value of true.
What's the easiest or most common way to solve this?
I'm OK to use any of the libraries immutability-helper, immutable-js etc or ES6. I've tried and googled this for over 4 hours, and still cannot wrap my head around it. Would be extremely grateful for some help.
You can use map to iterate the data and check for the fieldName, if fieldName is cityId then you need to change the value and return a new object otherwise just return the same object.
Write it like this:
var data = [
{fieldName: 'title', valid: false},
{fieldName: 'description', valid: true},
{fieldName: 'cityId', valid: false},
{fieldName: 'hostDescription', valid: false},
]
var newData = data.map(el => {
if(el.fieldName == 'cityId')
return Object.assign({}, el, {valid:true})
return el
});
this.setState({ data: newData });
Here is a sample example - ES6
The left is the code, and the right is the output
Here is the code below
const data = [
{ fieldName: 'title', valid: false },
{ fieldName: 'description', valid: true },
{ fieldName: 'cityId', valid: false }, // old data
{ fieldName: 'hostDescription', valid: false },
]
const newData = data.map(obj => {
if(obj.fieldName === 'cityId') // check if fieldName equals to cityId
return {
...obj,
valid: true,
description: 'You can also add more values here' // Example of data extra fields
}
return obj
});
const result = { data: newData };
console.log(result);
this.setState({ data: newData });
Hope this helps,
Happy Coding!
How about immutability-helper? Works very well. You're looking for the $merge command I think.
#FellowStranger: I have one (and only one) section of my redux state that is an array of objects. I use the index in the reducer to update the correct entry:
case EMIT_DATA_TYPE_SELECT_CHANGE:
return state.map( (sigmap, index) => {
if ( index !== action.payload.index ) {
return sigmap;
} else {
return update(sigmap, {$merge: {
data_type: action.payload.value
}})
}
})
Frankly, this is kind of greasy, and I intend to change that part of my state object, but it does work... It doesn't sound like you're using redux but the tactic should be similar.
Instead of storing your values in an array, I strongly suggest using an object instead so you can easily specify which element you want to update. In the example below the key is the fieldName but it can be any unique identifier:
var fields = {
title: {
valid: false
},
description: {
valid: true
}
}
then you can use immutability-helper's update function:
var newFields = update(fields, {title: {valid: {$set: true}}})

In React, how do I name a field of my form that is part of an array?

I'm building a React 16.13.0 application. In my form, I want to submit data (an address) as part of an array, so I set up my state like so ...
constructor(props) {
super(props);
this.state = {
countries: [],
provinces: [],
errors: [],
newCoop: {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
country: FormContainer.DEFAULT_COUNTRY,
}],
enabled: true,
email: '',
phone: '',
web_site: ''
},
I then created these functions for managing changes to the input fields ...
  handleInput(e) {
    let self=this
    let value = e.target.value;
    let name = e.target.name;
    this.setValue(self.state.newCoop,name,value)
  }
  setValue = (obj,is, value) => {
       if (typeof is == 'string')
         return this.setValue(obj,is.split('.'), value);
       else if (is.length === 1 && value!==undefined) { 
         return this.setState({obj: obj[is[0]] = value});
       } else if (is.length === 0)
         return obj;
       else
         return this.setValue(obj[is[0]],is.slice(1), value);
  }
...
                <Input inputType={'text'}
                   title= {'Street'} 
                   name= {'addresses[0].formatted'}
                   value={this.state.newCoop.addresses[0].formatted} 
                   placeholder = {'Enter address street'}
                   handleChange = {this.handleInput}
                   errors = {this.state.errors} 
                  /> {/* Address street of the cooperative */}
The Input.jsx file looks like the below ...
const Input = (props) => {
    return (  
  <div className="form-group">
      <FormLabel>{props.title}</FormLabel>
      <FormControl
            isInvalid={props.errors && Boolean(props.errors[props.name])}
            type={props.type}
            id={props.name}
            name={props.name}
            value={props.value}
            placeholder={props.placeholder}
            onChange={props.handleChange}
          />
      {props.errors && props.errors[props.name] && (
          <FormControl.Feedback type="invalid">
                 {props.errors[props.name].map((error, index) => (
                     <div key={`field-error-${props.name}-${index}`} className="fieldError">{error}</div>
                 ))} 
          </FormControl.Feedback>
      )}
  </div>
    )
}
export default Input;
However, when I attempt to change the value, I get the below error. I'm not sure what else I need to be doing to name my component such that I can successfully change it's value. I would prefer not to change the data structure in my constructor, but I'm willing to if that's what it takes.
TypeError: Cannot set property 'formatted' of undefined
FormContainer.setValue
src/containers/FormContainer.jsx:127
124 | if (typeof is == 'string')
125 | return this.setValue(obj,is.split('.'), value);
126 | else if (is.length === 1 && value!==undefined) {
> 127 | return this.setState({obj: obj[is[0]] = value});
| ^
128 | } else if (is.length === 0)
129 | return obj;
130 | else
ISSUE:
Cannot set property 'formatted' of undefined
// Reason : because you can't access obj["addresses[0]"]["formatted"]
// Solution : it should look something like obj["addresses"][0]["formatted"]
Because you are splitting up string by ., so a result you are getting
[
"addresses[0]",
"formatted"
]
Now that you have successfully splitted up the string ,
You are trying to get object by name, specifically obj["addresses[0]"], But you can't access the object index like this,
It will give you undefined, so as a result, you are getting the above error. you can check that exact error by running below code snippet,
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(".")
console.log("obj['addresses[0]'] ===>" , obj[names[0]])
console.log("obj['addresses[0]']['formatted'] ===>" , obj[names[0]][names[1]])
SOLUTION :
So now question is if not obj["addresses[0]"] this then what, the solution is obj["addresses"]["0"],
So you have 2 options :
First : change this addresses[0].formatted to addresses.0.formatted
Second : you need to split the sting with .split(/[\[\].]+/)
I would prefer second option as this addresses[0].formatted looks real form name, and this is how it should look like, you can check that in below code snippet also.
const obj = {
name: '',
types: [],
addresses: [{
formatted: '',
locality: {
name: '',
postal_code: '',
state: ''
},
}],
};
const names = "addresses[0].formatted".split(/[\[\].]+/)
console.log("obj['addresses'] ==>" , obj[names[0]])
console.log("obj['addresses']['0'] ==>" , obj[names[0]][names[1]])
console.log("obj['addresses']['0']['formatted'] ==>" , obj[names[0]][names[1]][names[2]])
NOTE :
Now, once you solved the issue, real issue come up in the picture, obj: obj[is[0]] = value, here obj is object so this will throw error , and also your setValue function is limited to that functionality only, it should be generic
handleInput = e => {
let name = e.target.name;
let value = e.target.value;
const keys = name.split(/[\[\].]+/);
this.setState(this.updateValue(this.state, keys, value));
};
// I've created a recursive function such that it will create a
// copy of nested object so that it won't mutate state directly
// obj : your state
// name : input name
// value : value that you want to update
updateValue = (obj, name, value, index = 0) => {
if (name.length - 1 > index) {
const isArray = Array.isArray(obj[name[index]]);
obj[name[index]] = this.updateValue(
isArray ? [...obj[name[index]]] : { ...obj[name[index]] },
name,
value,
index + 1
);
} else {
obj = { ...obj, [name[index]]: value };
}
return obj;
};
WORKING DEMO :
Your code is quite confusing, that's part of your problem to begin with, the other problem with your code is that it is not good practice to have nested objects in react's state. You can learn more by reading this answer in this other question.
Here is an example of what you could do with your code to set the state, however, notice that this is a bad way of solving the issue:
handleInput(e) {
let value = e.target.value;
this.setState(prevState =>{
...prevState,
newCoop: {
...prevState.newCoop
addresses: [
{
...prevState.newCoop[0].addresses
formatted: value
}
]
}
})
}

How to save an empty array as property of a nested object in mongoose?

I have the following code:
var lesson = new Lesson({
classroomId: req.params.classroomId,
name: req.body.name,
startDate: startDate1,
endDate: endDate1,
teacher: {
_id: classroom.teacher._id,
attendance: [],
},
students: [],
});
lesson.save();
When I check in the backend, the teacher key just has the _id property and the attendance property wasnt saved. I suspect this is because it is an empty array. How can I save an empty array like this?
It may be because Lesson Schema is not defined properly. Did you try setting...
mongoose.Schema({
.....
teacher: {
...
attendance: { type: Array, default: [] }
}
...
});

Binding values to panel title

I have a problem when I am binding values to a panel title.
My code basically looks like this :
Ext.define('serviceteamWorkflow.view.core.ServiceteamWorkflow', {
extend: 'Ext.Panel',
bind: {
title: 'First Name : {firstName} Last Name : {lastName}'
},
})
The problem is that nothing shows in the title when one of the bound values is null or undefined. Ie. If one of the bound values is invalid then the whole thing won't show.
I would like to just show nothing if the bound value is invalid. ie :
First Name : Last Name : Doe
Is there a way around this?
You could create a formula that would reference your bindings in your view model:
Ext.define('serviceteamWorkflow.view.core.ServiceteamWorkflow', {
extend: 'Ext.Panel',
bind: {
title: {showTitle}
},
})
Then inside your ServiceteamWorkflow view model:
requires: [
'Ext.app.bind.Formula'
],
data: {
firstName: '',
lastName: 'Johnson'
},
formulas: {
showTitle: function(get) {
var firstName = get('firstName'),
lastName = get('lastName');
return "First Name : " + firstName + " Last Name: " + lastName;
}
}
For those who are like me wondering how to set default values to viewmodel as suggested by Evan Trimboli in the first comment, it should be set in a view:
viewModel: {
data: {
firstName: '',
lastName: ''
}
}
If you set your data somewhere dynamically, for example:
this.getViewModel().set('fullName', data);
you need to set defaults like this:
viewModel: {
data: {
fullName: {
firstName: '',
lastName: ''
}
}
}

Angular Shorthand on $scope for saving data

is there any shorthand for something like this?
var data =
{
name: $scope.admin.name,
email: $scope.admin.email,
roles: $scope.admin.roles
};
Usually after i query and input to model i can just use like this:
$scope.admin = {
name: value1,
email: value2,
roles: value3
}
Edited:
My exact question inside var data how can i make it more simple like above without keep typing "$scope.admin".
Thanks
If you do not want a deep copy with angular.copy(), but just want to type less signs in code, you can do
var x = $scope.admin;
var data =
{
name: x.name,
email: x.email,
roles: x.roles
};
If you need to copy all the properties, use angular.copy:
angular.copy($scope.admin, $scope.user)
If you need to pick a subset of properties, a library like lodash might be useful. You would use the pick function:
$scope.admin = {
firstname: 'John',
name: 'Doe',
email: 'john#mycompany.com',
roles: ['sysadmin']
};
$scope.user = _.pick($scope.admin, ['name', 'email', 'roles']);
// -> {name: 'Doe', email: 'john#mycompany.com', roles: ['sysadmin']}

Resources