I have an object student, it has property id, name, groupName.
allStudents is the array of student objects, I want to turn it to an id to student map, but for each student, I want to generate a new property "label", if the student has groupName, label value is "name + groupName", otherwise it is name. So I write below code, it works:
const idsToStudents = allStudents.reduce((tempMap, student) => {
const getStudentLabel = (student) => {
if (student.groupName) {
return [student.name, `(${student.groupName})`].join(' ');
}
return student.name;
};
const studentLabel = getStudentLabel(student);
return {
...tempMap,
[student.id]: { ...student, label: studentLabel}
};
}, {});
I define getStudentLabel function inside reducer function, is there a better way to do this instead of declare getStudentLabel function again and again in the reducer function? You can ignore what exactly getStudentLabel does, just think it takes each person as parameter and return something based on person, is there a way to define the function only once, but still I can call it for each person in the reducer?
Thank you!
You are passing student as a parameter to the function, so you don't need to declare it inside the reduce. This would work as well:
const getStudentLabel = (student) => {
if (student.groupName) {
return [student.name, `(${student.groupName})`].join(' ');
}
return student.name;
};
const idsToStudents = allStudents.reduce((tempMap, student) => {
const studentLabel = getStudentLabel(student);
return {
...tempMap,
[student.id]: { ...student, label: studentLabel}
};
}, {});
And you can also shorten the code a bit:
const getStudentLabel = ({ name, groupName }) => groupName
? `${name} (${groupName})`
: name;
const idsToStudents = allStudents.reduce((tempMap, student) => ({
...tempMap,
[student.id]: { ...student, label: getStudentLabel(student) }
}), {});
I wouldn't worry about redefining functions inside closures. Any reasonable javascript implementation will optimize that so that minimal extra memory is being used. I wouldn't say its accurate to say that you're "defining" the function more than once - you are only defining it once in your code. The function is being instantiated each time, but this instantiation will take advantage of caches of the static parts of the the function. So your way of doing it is perfectly fine. As Ori Drori mentioned, you don't have to have the student variable in the inner function, but it might be a good idea to anyway, so that you're very explicit about the function's dependencies.
Object.fromEntries can work instead of reduce.
const getStudentLabel = ({name, groupName}) => groupName
? name + ` (${groupName})`
: name;
const idsToStudents = Object.fromEntries(
allStudents.map(student => [student.id, { ...student, label: getStudentLabel(student) }])
);
Related
I am using eslint and getting this error.
Expected to return a value in arrow function
The error is showing on the third line of the code.
useEffect(() => {
let initialPrices = {};
data.map(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
The map function must return a value. If you want to create a new object based on an array you should use the reduce function instead.
const reducer = (accumulator, { category, options }) => (
{...accumulator, [category]:options[0].price}
)
const modifiedData = data.reduce(reducer)
More information https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
The map function is intended to be used when you want to apply some function over every element of the calling array. I think here it's better to use a forEach:
useEffect(() => {
let initialPrices = {};
data.forEach(({ category, options }) => {
initialPrices = {
...initialPrices,
[category]: options[0].price,
};
});
setSelectedPrice(initialPrices);
}, []);
Your map function should return something. Here it's not the case so the error happens. Maybe a reduce function will be more appropriate than map?
From what I can see in your case, is that you want to populate initialPrices, and after that to pass it setSelectedPrice. The map method is not a solution, for you in this case, because this method returns an array.
A safe bet in your case would a for in loop, a forEach, or a reduce function.
const data = [
{
category: "ball",
options: [
{
price: "120.45"
}
]
},
{
category: "t-shirt",
options: [
{
price: "12.45"
}
]
}
];
The forEach example:
let initialPrices = {};
// category and options are destructured from the first parameter of the method
data.forEach(({ category, options}) => {
initialPrices[category] = options[0].price;
});
// in this process I'm using the Clojure concept to add dynamically the properties
setSelectedPrice(initialPrices);
The reduce example:
const initialPrices = Object.values(data).reduce((accumulatorObj, { category, options}) => {
accumulatorObj[category] = options[0].price
return accumulatorObj;
}, {});
setSelectedPrice(initialPrices);
I am doing an E-shop template in ReactJS to practice, everything is done but I can't figure out what is the best way to remove one item from state in form of array with objects.
Here is an example of what I am trying to do:
Defining my state:
const [cartData, setCartData] = useState([])
Adding items to it:
const exampleFunction = (heading, price) => {
setCartData([...cartData, {name: heading, price: price}])
}
All of this is working just fine so my state after adding some items looks like this:
[{name: "White T-shirt", price: "$20"}, {name: "Red T-shirt", price: "$20"}]
Now my question is, if user clicks the delete button and I pass to my delete function the name parameter, how do I remove the one item with received name? Here is what I was trying to do but it didn't really work out:
const deleteItem = (name) => {
var newList = []
cartData.map(d => {
if (d.name !== name) {
newList.push(d.name)
}
return d
})
setCartData([])
newList.map(item => {
setCartData([...cartData, {name: item.name}])
})
}
Like I said, I must've done it wrong because this is not doing the job for me. What is the best way to go about deleting one item from state? (please don't try to repair my code if there is better/smarter solution)
Thank you!
What you want is the filter array prototype method.
const deleteItem = (name) => {
setCartData((state) => state.filter((item) => item.name !== name))
}
When using the current state value to update state, it's good practice to pass a callback function to setState.
This should remove the entry with the specified name from cardData:
const deleteItem = (name) => {
const newCartData = cartData.filter((d) => d.name !== name);
setCartData(newCartData);
};
You need to use filter() method of array. According to MDN web docs, the filter() method creates a new array with all elements that pass the test implemented by the provided function.
const deleteItem = (name) => {
const newList = cartData.filter(d => d.name !== name);
setCartData(newList);
}
I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.
I have a JavaScript object with some nested properties that I want to update based on some conditions. The starting object could be something like:
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
I have come up with a solution using Object.keys, reduce and the spread operator, but I would like to know if this is the best / more concise way as of today or if there is a better way. I'm not looking for the most performing option, but for a "best practice" (if there is one) or a more elegant way.
EDIT 30/01/20
As pointed out in the comments by #CertainPerformance my code was mutating the original options variable, so I am changing the line const option = options[key]; to const option = { ...options[key] };. I hope this is correct and that the function is not mutating the original data.
const newObject = Object.keys(options).reduce((obj, key) => {
const option = { ...options[key] };
const newVal = getNewValue(option.label); // example function to get new values
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
option.selections.value = newVal;
}
return {
...obj,
[key]: option,
};
}, {});
getNewValue is an invented name for a function that I am calling in order to get an 'updated' version of the value I am looking at. In order to reproduce my situation you could just replace
the line const newVal = getNewValue(option.label); with const newVal = "bla bla";
Since you tagged this q with functional-programming here is a functional approach. Functional Lenses are an advanced FP tool and hence hard to grasp for newbies. This is just an illustration to give you an idea of how you can solve almost all tasks and issues related to getters/setters with a single approach:
// functional primitives
const _const = x => y => x;
// Identity type
const Id = x => ({tag: "Id", runId: x});
const idMap = f => tx =>
Id(f(tx.runId));
function* objKeys(o) {
for (let prop in o) {
yield prop;
}
}
// Object auxiliary functions
const objSet = (k, v) => o =>
objSetx(k, v) (objClone(o));
const objSetx = (k, v) => o =>
(o[k] = v, o);
const objDel = k => o =>
objDelx(k) (objClone(o));
const objDelx = k => o =>
(delete o[k], o);
const objClone = o => {
const p = {};
for (k of objKeys(o))
Object.defineProperty(
p, k, Object.getOwnPropertyDescriptor(o, k));
return p;
};
// Lens type
const Lens = x => ({tag: "Lens", runLens: x});
const objLens_ = ({set, del}) => k => // Object lens
Lens(map => ft => o =>
map(v => {
if (v === null)
return del(k) (o);
else
return set(k, v) (o)
}) (ft(o[k])));
const objLens = objLens_({set: objSet, del: objDel});
const lensComp3 = tx => ty => tz => // lens composition
Lens(map => ft =>
tx.runLens(map) (ty.runLens(map) (tz.runLens(map) (ft))));
const lensSet = tx => v => o => // set operation for lenses
tx.runLens(idMap) (_const(Id(v))) (o);
// MAIN
const options = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
}
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
}
}
};
const nameLens = lensComp3(
objLens("formatOption"))
(objLens("selections"))
(objLens("name"));
const options_ = lensSet(nameLens) ("foo") (options).runId;
// deep update
console.log(options_);
// reuse of unaffected parts of the Object tree (structural sharing)
console.log(
options.heightOptions === options_.heightOptions); // true
This is only a teeny-tiny part of the Lens machinery. Functional lenses have the nice property to be composable and to utilize structural sharing for some cases.
If you want to set a value for a nested property in a immutable fashion,
then you should consider adopting a library rather than doing it manually.
In FP there is the concept of lenses
Ramda provides a nice implementation: https://ramdajs.com/docs/
const selectionsNameLens = R.lensPath(
['formatOption', 'selections', 'name'],
);
const setter = R.set(selectionsNameLens);
// ---
const data = {
formatOption: {
label: 'Model Format',
selections: {
name: 'Specific Format',
value: '12x28',
},
},
heightOption: {
label: 'Model Height',
selections: {
name: 'Specific Height',
value: '15',
},
},
};
console.log(
setter('Another Specific Format', data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
The first comment from CertainPerformance made me realize that I was mutating the original options variable. My first idea was to make a copy with the spread operator, but the spread operator only makes a shallow copy, so even in my edit I was still mutating the original object.
What I think is a solution is to create a new object with only the updated property, and to merge the two objects at the end of the reducer.
EDIT
The new object also needs to be merged with the original option.selections, otherwise I would still overwrite existing keys at that level (ie I would overwrite option.selections.name).
Here is the final code:
const newObject = Object.keys(options).reduce((obj, key) => {
const option = options[key];
const newVal = getNewValue(option.label); // example function to get new values
const newOption = {}; // create a new empty object
// update based on existence of new value and key
if (option.selections && option.selections.value && newVal) {
// fill the empty object with the updated value,
// merged with a copy of the original option.selections
newOption.selections = {
...option.selections,
value: newVal
};
}
return {
...obj, // accumulator
[key]: {
...option, // merge the old option
...newOption, // with the new one
},
};
}, {});
A more concise version that has been suggested to me would be to use forEach() instead of reduce(). In this case the only difficult part would be to clone the original object. One way would be to use lodash's _.cloneDeep(), but there are plenty of options (see here).
Here is the code:
const newObject = _.cloneDeep(options);
Object.keys(newObject).forEach(key => {
const newVal = getNewValue(newObject[key].label); // example function to get new values
// update based on existence of new value and key
if (newObject[key].selections && newObject[key].selections.value && newVal) {
newObject[key].selections.value = newVal;
}
});
The only problem is that forEach() changes values that are declared outside of the function, but reduce() can mutate its parameter (as it happened in my original solution), so the problem is not solved by using reduce() alone.
I'm not sure that this is the best solution, but it surely is much more readable for the average developer than my first try or the other solutions.
I'm new to react and as well to the terms of functional, imperative, declarative. And I get to know that pure function is easy to test. I am self taught to program with Javascript. So far, it is working but my goal is to learn to write clean and maintainable code.
my question is the method addProductToSaleList below is bad and untestable because it is imperative? and how can I do it differently.
class SaleComponent extends React.Component {
addProductToSaleList = (values, dispatch, props) => {
//filter product from productList
const productFound = props.productList.filter(product => {
if (values.productCode === product.code.toString()) {
return product
}
return undefined
})[0]
if (productFound) {
// filter sale list to check if there is already product in the list.
const detailFound = props.saleItem.details.filter(detail => {
if (productFound.name === detail.product) {
return detail
}
return undefined
})[0]
// if it is exist just increment the qty
if (detailFound) {
const { sub_total, ...rest } = detailFound
props.dispatcher('UPDATE_SALEDETAIL_ASYNC', {
...rest,
qty: parseInt(detailFound.qty, 10) + 1
})
// if it is not exist add new one
} else {
props.dispatcher('ADD_SALEDETAIL_ASYNC', {
product: productFound.id,
price: productFound.price,
qty: 1
})
}
} else {
alert('The product code you add is not exist in product list');
}
}
render() {
// Render saleList
}
}
I belive this question should go to Code Review, but I will give it a shot. Part of the code can be improved
const productFound = props.productList.filter(product => {
if (values.productCode === product.code.toString()) {
return product
}
return undefined
})[0]
First, filter function receives a callback and for each item that callback will be executed. If the callback returns a value interpreted as true, it will return the item in the new array the function will build. Otherwise, it will skip that item. Assuming you're trying to find one item in the code, you could use the function find which will return you that element directly (no need for [0]), or undefined if that item is not found. So your code could be rewrite to
const productFound = props.productList.find(product => values.productCode === product.code.toString());
Note: No IE support.
Then, if the value was not found, you could just alert and do an early return. (You might also want to handle errors differently, with a better format than plain alert).
The code would look like
if (!productFound) {
alert('The product code you add is not exist in product list');
return;
}
// rest of the function
in order to find details, you can use find method as well
const detailFound = props.saleItem.details.find(detail => productFound.name === detail.product);
and then just call the rest of the code
// if it is exist just increment the qty
if (detailFound) {
const { sub_total, ...rest } = detailFound
props.dispatcher('UPDATE_SALEDETAIL_ASYNC', {
...rest,
qty: parseInt(detailFound.qty, 10) + 1
})
// if it is not exist add new one
} else {
props.dispatcher('ADD_SALEDETAIL_ASYNC', {
product: productFound.id,
price: productFound.price,
qty: 1
})
}
Another improvement:
You're receiving a dispatch function as a parameter, but you're not using it. So you could remove it from function's declaration
(values, props) => { ... }
And you could split the last part into two different functions, something like
const getAction = details => `${detailFound ? 'UPDATE' : 'ADD'}_SALEDETAIL_ASYNC`;
const getObject = (details, productFound) => {
if (!details) {
return {
product: productFound.id,
price: productFound.price,
qty: 1
};
}
const { sub_total, ...rest } = detailFound;
return {
...rest,
qty: parseInt(detailFound.qty, 10) + 1
};
}
and then just call
props.dispatcher(getAction(details), getObject(details, productFound));
The end result would look like
addProductToSaleList = (values, props) => {
//filter product from productList
const productFound = props.productList.find(product => values.productCode === product.code.toString());
if (!productFound) {
alert('The product code you add is not exist in product list');
return;
}
// filter sale list to check if there is already product in the list.
const detailFound = props.saleItem.details.find(detail => productFound.name === detail.product);
const getAction = details => `${details ? 'UPDATE' : 'ADD'}_SALEDETAIL_ASYNC`;
const getObject = (details, productFound) => {
if (!details) {
return {
product: productFound.id,
price: productFound.price,
qty: 1
};
}
const { sub_total, ...rest } = details;
return {
...rest,
qty: parseInt(details.qty, 10) + 1
};
}
props.dispatcher(getAction(details), getObject(details, productFound));
}
my question is the method addProductToSaleList below is bad and
untestable because it is imperative
Well your code is testable, there are no external dependencies. So you could pass mocked values and props and add unit tests to that. That means, passing a fake values and props (they are just plain js object) and make assertions over that.
For instance:
You could mock dispatcher function and given the fake values in productList and saleItem.details you could see if dispatcher is called with the proper values. You should test different combinations of that
Mock alert function (Again, I would use another UI approach) and verify it is called, and that no other code is called (asserting that your fake dispatcher is not called). Something like this:
let actionToAssert;
let objectToAssert;
let values = { productCode: 'somecode' };
let props = {
productList: // your item listm with id and price, name, etc,
saleItem: {
details: // your details array here
}
dispatcher: (action, newObject) => {
actionToAssert = action;
objectToAssert = newObject;
}
}
addProductToSaleList(values, props); // make here assertions over actionToAssert and objectToAssert