pick and omit in one function (lodash) in Redux action object - javascript

Basically what I need is to get "items" from json and re-assign it, and leave other keys untouched:
The first approach duplicates data.
The second seems to be bad in terms of performance.
And the third is hard to read and understand.
I use lodash. But if it can't be done in a clever fashion, you can suggest me a different library.
function a(name, json) {
return {
type: RECEIVE_DATA,
name,
items: _.get(json, 'data.items', []),
receivedAt: Date.now(),
...json,
};
}
function b(name, json) {
return {
..._.omit(json, 'data'),
type: RECEIVE_DATA,
name,
items: _.get(json, 'data.items', []),
receivedAt: Date.now(),
}
}
function c(name, json) {
return {
..._.transform(json, (result, value, key) =>{
if (key === 'data') {
result['items'] = value['items'];
} else {
result[key] = value;
}
}, {}),
type: RECEIVE_DATA,
name,
receivedAt: Date.now(),
}
}

Use parameters destructuring to get items, and rest properties to collect the rest of the params:
const json = { another: [], data: { items: [1] } };
function a(name, { data, data: { items = [] }, ...jsonRest } = {}) {
return {
type: 'RECEIVE_DATA',
name,
items,
receivedAt: Date.now(),
...jsonRest
};
}
console.log(a('name', json));
You can also shorten the action creator a bit by using an arrow function:
const a = (name, { data, data: { items = [] }, ...jsonRest } = {}) => ({
type: RECEIVE_DATA,
name,
items,
receivedAt: Date.now(),
...jsonRest
});

If you don't mind having a couple more lines in your function, you could easily just grab the data from that key then delete it:
function a(name, json = {}) {
const { items = [] } = json.data;
delete json.data;
return {
type: RECEIVE_DATA,
name,
items,
receivedAt: Date.now(),
...json,
};
}

Related

Object name from array value

I have array with some values :
let typeArray = ["name", "strret", "car", "type"];
And I have one object :
let formDefinitionObject = {schema:{}}
I want the formDefinitionObject object be like :
let formDefinitionObject = {
schema: {
name: {
type: 'string',
title: 'Name',
required: true
},
strret: {
type: 'string',
title: 'Strret'
},
car: {
type: 'string',
title: 'Car'
},
type: {
type: 'string',
title: 'Type'
}
}
}
I want dynamically for each item in array to be object in formDefinitionObject.schema object. For example if i add one more item in array typeArray.push('country') to automatic add this object in formDefinitionObject.schema object.
Couldn't understand how required: true would fit in. Remaining things can be done as follows
var getFormDefinition = function(arr) {
function getSchemaObj(arr) {
return arr.map(d => ({
[d]: {
type: typeof(d),
title: d
}
}))
.reduce((a, b) => ({ ...a, ...b }))
}
var schemaObj = getSchemaObj(arr)
arr.push = function(...d) {
Object.assign(schemaObj, getSchemaObj(d))
return Array.prototype.push.call(arr, ...d)
}
return ({
schema: schemaObj
})
}
var typeArray = ["name", "strret", "car", "type"];
var result = getFormDefinition(typeArray)
console.log(result)
typeArray.push('nitish')
console.log(result)
Even though you did not clarify more how a field has to be required or does any field has to be as string, here's a solution based on what you provided so far.
The next snippet does the job and it has some explanations :
let typeArray = ['name', 'strret', 'car', 'type'],
formDefinitionObject = {
schema: {}
};
/** cycle through "typeArray" and populate "formDefinitionObject.schema" **/
typeArray.forEach(el => {
let currObj = {
type: 'string', /** YOU DID NOT SPECIY HOW TO DECIDE THE TYPE **/
title: el[0].toUpperCase() + el.substring(1), /** the title with the first letter being capitalized as you provided in the question. You can just use "el" instead of "el[0].toUpperCase() + el.substring(1)" if you'd like to print as it is **/
};
el === 'name' && (currObj['required'] = true); /** YOU DID NOT SPECIY HOW TO DECIDE IF A FIELD HAS TO BE REQUIRED. I just saw only the "name" as required so I did a basic (yet a stupid) check if the current element is "name" add a required to it **/
formDefinitionObject.schema[el] = currObj; /** add the current record to the "schema" attribute **/
});
console.dir(formDefinitionObject); /** printing the result **/
I'll be here if you answer our questions in the comments section.
Til then, hope I pushed you further.
You could use Proxy on the typeArray with the set trap so each time you push new value to the proxy array you can also add new property to your schema. This way you can simulate observer pattern.
You can also create some pattern to add additional properties like required for example name:prop1:prop2 but this way value is fixed to true.
let typeArray = ["name:required", "strret", "car", "type"];
let formDefinitionObject = {
schema: {}
}
let proxyArray = new Proxy(typeArray, {
set(obj, prop, value) {
if (prop != 'length') addToSchema(formDefinitionObject.schema, value);
return Reflect.set(...arguments);
}
})
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function addToSchema(schema, prop) {
const [name, ...params] = prop.split(':');
schema[name] = {
type: 'string',
title: capitalize(name)
}
params.forEach(param => schema[name][param] = true);
return schema;
}
proxyArray.reduce(addToSchema, formDefinitionObject.schema);
proxyArray.push('email:required:isEmail');
proxyArray.push('phone');
console.log(formDefinitionObject)
Update: You could use something like this name:prop1|value:prop2 to add property value other then true but if you don't specify value default is still true
let typeArray = ["name:required", "strret", "car", "type"];
let formDefinitionObject = {
schema: {}
}
let proxyArray = new Proxy(typeArray, {
set(obj, prop, value) {
if (prop != 'length') addToSchema(formDefinitionObject.schema, value);
return Reflect.set(...arguments);
}
})
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function addToSchema(schema, prop) {
const [name, ...params] = prop.split(':');
schema[name] = {
type: 'string',
title: capitalize(name)
}
params.forEach(param => {
const [key, value] = param.split('|');
schema[name][key] = value ? value : true
});
return schema;
}
proxyArray.reduce(addToSchema, formDefinitionObject.schema);
proxyArray.push('email:required:isEmail');
proxyArray.push('phone:default|123/555-333:required');
proxyArray.push('address')
console.log(formDefinitionObject)

Inserting new element into empty JSON object

I'm trying to insert data into an empty JSON array, but am having trouble. I'm defining the array in the constructor, then making a couple get requests to the back-end when the page loads, and after getting the response I want to add the new array element to the existing. This is the code I am using:
constructor() {
super();
this.state = {
sds: []
}
}
componentDidMount() {
axios.get('/userData', {
params: {
user: this.props.auth.user.name
}
}).then(res => {
for(var i=0; i<res.data[0].chemID.split(',').length; i++){
if(res.data[0].chemID.split(',')[i] != 0){
axios.get('/chemData', {
params: {
id: res.data[0].chemID.split(',')[i]
}
//This is where I want to insert the data
}).then(res => this.sds += ({
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
}))
}
}
})
}
+= doesn't work like that. Use a spread operator to copy the previous contents of the array, then add the new object in manually -
}).then((res) => {
const newThing = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
};
this.setState(prevState => ({
sds: [...prevState.sds, newThing]
}))
}
You should never try to mutate the state yourself, always use setState. In this case, you can pass a function as the first parameter, which provides the previous state. That way, you can ensure whatever was in this.state.sds is preserved, and your new object is added to that array.
You can use array.push().
this.state.sds.push(obj);
If you are not using react setState method than, you need to refer any state variable using this.state.variableName.
You need to add to your array by using the push() method like this:
constructor() {
super();
this.state = {
sds: []
}
}
componentDidMount() {
axios.get('/userData', {
params: {
user: this.props.auth.user.name
}
}).then(res => {
for(var i=0; i<res.data[0].chemID.split(',').length; i++){
if(res.data[0].chemID.split(',')[i] != 0){
axios.get('/chemData', {
params: {
id: res.data[0].chemID.split(',')[i]
}
//This is where I want to insert the data
}).then(res => {
this.state.sds.push({
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
})
})
}
}
})
}
You can try using the next example:
this.state.sds[this.state.sds.length] = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
}
[Edited]
Like #larz said, you must use the setState method to avoid unexpected result of your code.
var newSds = this.stage.sds;
newSds[newSds.length] = {
id: i,
title: res.data[0].chemName,
selected: false,
key: 'sds'
};
this.setState({ sds: newSds});
You can get more information about the lifecycle in react here and the "state updates are merged" here

Keep original key when re-looping through nested object

I've got a nested object with child objects and arrays. Something like this:
const documents = {
invoice: {
documentID: '_e4564',
displayName: '2019-02-03',
url: 'https://www.urltoinvoice.com'
},
conditions: {
documentID: '_e9365',
displayName: 'Conditions company x',
url: 'https://www.urltoconditions.com'
},
reminders: [
{
documentID: '_e4364',
displayName: 'First reminder',
url: 'https://www.urltofirstreminder.com'
},
{
documentID: '_e0254',
displayName: 'Second reminder',
url: 'https://www.urltosecondreminder.com'
},
]
}
I'm trying to create a new array of objects to use in a select box.
The child objects need the same properties but with an updated displayName based on the document type. So, for example, reminder: First reminder .
Currently, this is my code:
const newArray = [];
this.addDocumentToArray(documents, newArray);
and the addDocumentToArray function:
addDocumentToArray = (documents, arr) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
this.addDocumentToArray(val, arr);
} else {
arr.push({ documentID: val.documentID, displayName: `${key}: ${val.displayName}` });
}
});
}
The output at this point is an array that looks like this:
0: {documentID: "_e4564", displayName: "invoice: 2019-02-03"}
1: {documentID: "_e9365", displayName: "conditions: Conditions company x"}
2: {documentID: "_e4364", displayName: "0: First reminder"}
3: {documentID: "_e0254", displayName: "1: Second reminder"}
Almost ok but the key of the reminders is 0 and 1. How can I get reminder (or reminders) as key?
You can add third optional parameter to function labelKey. You are passing that parameter only if your value is array and it will use that value as key in else part
addDocumentToArray = (documents, arr, labelKey) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
this.addDocumentToArray(val, arr, key);
} else {
arr.push({ documentID: val.documentID, displayName: `${labelKey || key}: ${val.displayName}` });
}
});
}
I really like chriss' answer, so I'll try to write an alternative:
let currentKey = null;
addDocumentToArray = (documents, arr) => {
Object.entries(documents).forEach(([key, val]) => {
if (Array.isArray(val)) {
let prevKey = currentKey;
currentKey = key;
this.addDocumentToArray(val, arr);
currentKey = prevKey;
} else {
arr.push({ documentID: val.documentID, displayName: `${currentKey || key}: ${val.displayName}` });
}
});
}

Updating state as a nested object

I have this type of state in my app
state = {
abc: true,
array: [
{ id: 12345, done: false },
{ id: 10203, done: false },
{ id: 54321, done: false }
]
};
I am looking for a solution to the following problem: I need to change done property accordingly to passed id like in the following function when something like this handle(12345) is passed as an argument to handle function :
handle = id => {
this.state.array.map(e => {
if (e.key === id) {
this.setState({array: [
{ id: id, done: true },
{ id: 10203, done: false },
{ id: 54321, done: false }
]})
}
});
};
In simple words I need to change just one object in array based on provided id.
Thanks for any help or tips!
I'd write the handle method as:
handle = id => {
this.setState(prevState => {
const { array } = prevState;
return {
array: [
...array.filter(o => o.id !== id),
{id, done: true}
]
};
});
};
The idea here is that, remove the matching id from old state, and then add a new object to the array with id and done property as {id, done: true}.
Once you are allowed to restructure state to be hashmap instead of array:
state = {
abc: true,
array: {
12345: { done: false },
10203: { done: false },
54321: { done: false }
]
};
then you will be able to use power of spread operator:
let id = 12345;
this.setState({
array: {
...this.state.array,
[id]: {
...this.state.array[id],
done: true
}
}
});
Otherwise using array.map() seems to be the only option
You can use this Redux pattern to return a new array with the element in question being changed, while keeping your array immutable:
handle = id => {
const updatedArray = this.state.array.map(e => {
if (e.key === id) {
return { id: id, done: true };
}
else {
return e;
}
});
this.setState({array: updatedArray});
};
This keeps your data structures immutable while changing only what you need to change.
var newArray = this.state.array;
for(var i = 0; i < newArray.length; i++){
if(newArray[i].id === 12345) {
newArray[i].done = true;
}
}
this.setState({array: newArray});
By creating the newArray here you will be avoiding directly touching the state element, so you can change anything you want inside it afterwards you can set the state.

Return object from forEach?

Is it possible to construct an object from the return of a forEach? How I'm doing:
const data = {}
object.rules.forEach(rule => {
data[rule.name] = { body: [], type: rule.type }
})
How I would like to do:
const data = object.rules.forEach(rule => {
data[rule.name] = { body: [], type: rule.type }
})
Consider using Array.reduce
const data = object.rules.reduce((obj, rule) => {
obj[rule.name] = { body: [], type: rule.type }
return obj;
}, {})
and shorter version
const data = object.rules.reduce((obj, rule) => Object.assign({}, obj, {[rule.name]:{ body: [], type: rule.type } }), {})
Whenever you want to convert an array into another structure that isn't an array, reduction is your friend.
console.log(
['a', 'b', 'c'].reduce((obj, char, index) => {
obj[char] = index;
return obj;
}, {})
)
No you can't use forEach for this purpose. Calling forEach on an array object applies the callback function you supply to forEach at each element in the array object.
You could use reduce for this purpose, which
applies a function against an accumulator and each element in the
array (from left to right) to reduce it to a single value
const data = object.rules.reduce(function(obj, rule) {
obj[rule.name] = { body: [], type: rule.type }
return obj;
}, {});
For a detailed explanation about this method please have a look here.
You could use Object.assign with a spread syntax ... and Array#map for single objects.
const data = Object.assign(...object.rules.map(
rule => ({ [rule.name]: { body: [], type: rule.type } }))
);
with destructuring assignment for name and type and short hand property
const data = Object.assign(...object.rules.map(
({ name, type }) => ({ [name]: { body: [], type } }))
);

Categories