Cant understand what is happening in this compose reduce example in javascript? - javascript

var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var compose = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
I don't understand this example well. I tried stepping through it with the debugger and concluded the following:
When I call function userPurchase the reduce will work and at its end f will be test2 and g will be addItemToCart then test2 is returned as the accumulated. Then we call it passing (user1, { name: 'laptop', price: 876 }) as arguments and g is called in it that is addItemToCart.
I don't understand how g is changed to applayTax, then addItemToPurchase, then empty every time function test2 call itself.
How or why this is happening?

The thing that may have gotten you confused is taking the term accumulator literally. By convention that's the name of the first argument to a reducer. But it's not necessary to use it to accumulate a value. In this case it is used to compose a series of functions.
The real meaning of the first argument to a reducer is previouslyReturnedValue:
function compose(previouslyReturnedValue, g) {
return function (...args) {
return previouslyReturnedValue(g(...args));
};
}
So let's walk through this loop:
[empty, addItemToPurchase, applayTax, addItemToCart].reduce(
(f,g) => {
return (...args) => {
return f(g(...args));
}
}
);
The first round of the loop, f = empty and g = addItemToPurchase. This will cause compose to return:
return (...args) => {
return empty(addItemToPurchase(...args));
}
Leaving the array to become: [applayTax, addItemToCart]
The second round of the loop f = (...args) => {return empty(addItemToPurchase(...args))} and g = applyTax. This will cause compose to return:
return (...args) => {
return empty(addItemToPurchase(applyTax(...args)));
}
We continue with this logic until we finally get compose to return the full chain of functions:
return (...args) => {
return empty(addItemToPurchase(applyTax(addItemToCart(...args))));
}
Alternate view of the same process
If the above is a bit hard to follow then let's name the anonymous function that becomes f in each loop.
In the first round we get:
function f1 (...args) {
return empty(addItemToPurchase(...args));
}
In the second round we get:
function f2 (...args) {
return f1(applyTax(...args));
}
In the final round we get:
function f3 (...args) {
return f2(addItemToCart(...args));
}
It is this function f3 that is returned by reduce(). So when you call the return value of reduce it will try to do:
f2(addItemToCart(...args))
Which will call addItemToCart() and then call f2 which will execute:
f1(applyTax(...args))
Which will call applyTax() and then call f1 which will execute:
empty(addItemToPurchase(...args))
Which will call addItemToPurchase() and then call empty()
TLDR
Basically all this is doing:
let tmp;
tmp = addItemToCart(args);
tmp = applyTax(tmp);
tmp = addItemToPurchase(tmp);
tmp = empty(tmp);
More readable version
There is a way to implement this logic which is more readable and easier to understand if we abandon reduce(). I personally like the functional array methods like map() and reduce() but this is one of those rare situations where a regular for loop may lead to much more readable and debuggable code. Here's a simple alternative implementation that does exactly the same thing:
function userPurchase(...fns) {
return function(...args) {
let result = args;
// The original logic apply the functions in reverse:
for (let i=fns.length-1; i>=0; i--) {
let f = fns[i];
result = f(result);
}
return result;
}
}
Personally I find the implementation of userPurchase using a for loop much more readable than the reduce version. It clearly loops through the functions in reverse order and keep calling the next function with the result of the previous function.

Does this help at all?
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
function compose(f, g) {
const composition = function(...args) {
console.log('f name', f.name);
console.log('g name', g.name);
return f(g(...args));
};
Object.defineProperty(composition, 'name', {
value: 'composition_of_' + f.name + '_and_' + g.name,
writable: false
});
return composition;
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
const result = userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
console.log(result);
Let's say you need to take in a number, add ten to it and then double it. You could write some functions like:
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = function(num) { return double(add_ten(num)); };
You could also write the same thing as:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = compose(double, add_ten);
console.log('typeof add_ten_and_double:', typeof add_ten_and_double);
console.log('add_ten_and_double:', add_ten_and_double(4));
Using compose, we've created a function that does the same thing as our original add_ten_and_double function. Does that make sense up to here? (Point A).
If we then decided to add five we could have:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
const add_ten_and_double_and_add_five = compose(compose(add_five, double), add_ten);
console.log('typeof add_ten_and_double_and_add_five :', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five :', add_ten_and_double_and_add_five(4));
Now we've run compose using a function and a function that was composed of two other functions, but what we've got back is still just a function that takes a number and returns a number. Does that make sense up to here? (Point B).
If we wanted to add a few more functions then we would end up with a lot of compose calls in our code, so we could just say "give me the composition of all of these functions", and it might look like:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
functions_to_compose = [add_five, double, add_ten];
let composition;
functions_to_compose.forEach(function(to_compose) {
if(!composition)
composition = to_compose;
else
composition = compose(composition, to_compose);
});
const add_ten_and_double_and_add_five = composition;
console.log('typeof add_ten_and_double_and_add_five:', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five:', add_ten_and_double_and_add_five(4));
The fns.reduce(compose); in userPurchase in your code is basically doing the same thing as the forEach loop here, but neater. Does that make sense why add_ten_and_double_and_add_five is a function that you can pass in a number and all of the operations from functions_to_compose are being applied (last to first) to the number? (Point C).

Renaming
Part of the problem is simply in naming. It would help to introduce one more function, and to rename two others.
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany(...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany(
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
userPurchase(user1, { name: 'laptop', price: 876 });
//=> {active: true, cart: [], name: "Nady", purchase: [{name: "laptop", price: 1138.8}]}
// other functions elided
composeTwo (originally called compose) is a function that accepts two functions and returns a new one which accepts some input, calls the second function with that input and then calls the first function with the result. This is straightforward mathematical composition of functions.
composeMany (which as originally called -- very confusingly -- userPurchase) extends this composition to work on a list of functions, using reduce to sequentially call each function on the result of the pipeline so far, starting with the arguments passed. Note that it works from the last item in the list to the first one.
We use this to define the new userPurchase, which passes empty, addItemToPurchase, applyTax and addItemToCart to pipeline. This returns a function that will then apply them in sequence, doing something equivalent to function (...args) {return empty1(addItemToPurchase(applyTax(addItemToCart(...args))))}
We can see this in action in this snippet:
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany (...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
console .log (
userPurchase(user1, { name: 'laptop', price: 876 })
)
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applyTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
.as-console-wrapper {max-height: 100% !important; top: 0}
Modern Syntax
However, I would find this much cleaner with a more modern JS syntax. This is very much equivalent, but cleaner:
const composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
// .. other functions elided
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
Here it should be obvious that composeTwo takes two functions and returns a function. And in knowing that and understanding .reduce, it should be clear that composeMany takes a list of functions and returns a new function. This version, which also applies this change to the remaining functions is available in this snippet:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (user) => {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart .map (item => ({ ...item, price: item .price * taxRate }))
return { ...user, cart: updatedCart };
}
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
const user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
console .log (
userPurchase (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
How reduce and composeTwo work together
Here we try to demonstrate how reduce works with composeTwo to compose multiple functions into one.
In the first step, the init parameter to reduce is missing, so JS uses the first value in the array as the initial one, and starts iterating with the second one. So reduce first calls composeTwo with empty and addItemToPurchase, yielding a function equivalent to
(...args) => empty (addItemsToPurchase (...args))
Now reduce passes that function and applyTax to compose, yielding a function like
(...args) => ((...args2) => empty (addItemsToPurchase (...args2))) (applyTax (...args))
Now this is structured like the following:
(x) => ((y) => f ( g (y)) (h (x))
where x represents ...args, y represents ...args2, f represents empty, g represents addItems, and h represents applyTax.
but the right-hand side is a function ((y) => f ( g ( y))) with the value h (x) applied to it. This is the same as replacing y in the body with h(x), yielding f (g (h (x))), so that this function is equivalent to (x) => f (g (h (x))), and by replacing our original values, this ends up as
(...args) => empty (addItemsToPurchase (applyTax ( ...args)))
Do note that this application of values to the function does not happen now when the function is being built. It will happen when the resulting function is called. In memory, this is still something like (...args) => ((...args2) => empty (addItems (...args2))) (applyTax (...args)). But this logical version shows how it will work.
Of course we now do it again for addItemToCart:
(...args) => ((...args2) => empty (addItemsToPurchase (applyTax ( ...args2)))) (addItemToCart (...args))
and by the same sort of application, we get the equivalent of
(...args) => empty (addItems (applyTax ( addItemToCart (...args))))
Which is the basic definition of the composition of those functions.
Cleaning up the tax rate
There's something strange about the hard-coded tax rate. We can fix that by making a parameter used in calling userPurchase. This also allows us to clean up the applyTax function:
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
// ...
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
Note that the curried nature of this parameter lets us choose to apply just this value to get back a tax-rate specific function:
const someDistrictPurchase = userPurchase (1.12) // 12% tax
someDistrictPurchase(user, item)
Which we can see in one more snippet:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
var user1 = { name: 'Nady', active: true, cart: [], purchase: []}
console .log (
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
Lessons
Function composition is an essential part of functional programming (FP). While it helps to have functions like composeTwo and composeMany, it is much better if they have understandable names. (Note that compose is a perfectly legitimate name for the first one. My change here was just to make the distinction with composeMany clearer.) The biggest problem, to my mind was the original name of userPurchase as a composition function. That confuses a lot of things.
Modern JS (arrow functions, rest/spread, and destructuring) makes for code that is not only more succinct but generally easier to understand.

Related

Run async/await function inside a reduce Javascript [duplicate]

This question already has answers here:
JavaScript array .reduce with async/await
(11 answers)
Closed 6 months ago.
I need to fetch values from another API using the guid inside this particular array, then group them together (hence I used reduce Javascript in this case)
However, I could not get those values sumEstimatedHours and sumWorkedHours as expected. Can someone suggest a method please?
export const groupProjectsByPM = (listOfProjects) => {
const dir = "./json";
const estimatedHours = fs.existsSync(dir)
? JSON.parse(fs.readFileSync("./json/phases.json", "utf-8"))
: null;
let sumWorkedHours, sumEstimatedHours;
const groupedProjects = listOfProjects?.reduce(
(
group,
{
guid,
projectOwner: { name: POName },
name,
customer: { name: customerName },
deadline,
calculatedCompletionPercentage,
}
) => {
listOfProjects.map(async (element, index) => {
// const element = listOfProjects[index];
sumWorkedHours = await getWorkhoursByProject(element?.guid).then(
(res) => {
return res.reduce((acc, cur) => {
return acc + cur.quantity;
}, 0);
}
);
const filteredEstimatedHours = estimatedHours.filter(
(item) => item.project.guid === element.guid
);
sumEstimatedHours = filteredEstimatedHours.reduce((acc, cur) => {
return acc + cur.workHoursEstimate;
}, 0);
group[POName] = group[POName] || [];
group[POName].push({
guid,
name,
POName,
customerName,
deadline,
calculatedCompletionPercentage,
sumEstimatedHours,
sumWorkedHours,
});
return group;
});
return group;
},
[]
);
return groupedProjects;
};
here is an example of async/await inside reduce:
let's assume that we have an array of numbers
const arrayOfNumbers = [2,4,5,7,6,1];
We are going to sum them using reduce function:
const sumReducer = async () => {
const sum = await arrayOfNumbers.reduce(async (promisedSum, num) => {
const sumAcc = await promisedSum
// any promised function can be called here..
return sumAcc + num
}, 0)
console.log(sum)
}
So the trick is to remember to await the accumulator inside the reduce function
export const groupProjectsByPM = async (listOfProjects) => {
const dir = "./json";
const estimatedHours = fs.existsSync(dir)
? JSON.parse(fs.readFileSync("./json/phases.json", "utf-8"))
: null;
let sumWorkedHours, sumEstimatedHours;
const groupedProjects = await listOfProjects?.reduce(
async (
promisedGroup,
{
guid,
projectOwner: { name: POName },
name,
customer: { name: customerName },
deadline,
calculatedCompletionPercentage,
}
) => {
listOfProjects.map(async (element, index) => {
//accumulator in your case is group
const group = await promisedGroup;
// const element = listOfProjects[index];
sumWorkedHours = await getWorkhoursByProject(element?.guid).then(
(res) => {
return res.reduce((acc, cur) => {
return acc + cur.quantity;
}, 0);
}
);
const filteredEstimatedHours = estimatedHours.filter(
(item) => item.project.guid === element.guid
);
sumEstimatedHours = filteredEstimatedHours.reduce((acc, cur) => {
return acc + cur.workHoursEstimate;
}, 0);
group[POName] = group[POName] || [];
group[POName].push({
guid,
name,
POName,
customerName,
deadline,
calculatedCompletionPercentage,
sumEstimatedHours,
sumWorkedHours,
});
return group;
});
return group;
},
[]
);
return groupedProjects;
};
Best of luck ...

Call a function with many arguments in JavaScript

I have a compose function what should be called with n number of function, depending by what function was added as parameter.
For example:
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
v + 'letter'
},
link: (v) => {
v + 'nr'
}
}
const compose = (...fns) =>
fns.reduce(
(prevFn, nextFn) =>
(...args) =>
nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(props);
console.log(res('test'))
So, i expect, if the const props = ['link', 'letter']; the compose function should be called like: const res = compose(mapp[letter], mapp[link]);, or if const props = ['letter'];, the compose function should be called as: compose(mapp[letter]). At the moment the code does not work as expeted. Question: How to fix the code and to get the expected result?
There was 2 problems with your code
Missing return statement(s) in the mapp object's functions (or remove the { and } - see below)
You needed to map the string array into references to the functions and spread (...) the result when calling compose
Working example:
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
return v + 'letter'
},
link: (v) => {
return v + 'nr'
}
}
const compose = (...fns) =>
fns.reduce(
(prevFn, nextFn) => (...args) => nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(...props.map(p => mapp[p]));
console.log(res('test'))
Regarding 1. above that object could be written as below:
const mapp = {
letter: (v) => v + 'letter',
link: (v) => v + 'nr'
}
Edit: The need to destructure the call to map is because of the way you defined compose to take discrete parameters rather than an array. ie, this would also work (and not need to destructure map)
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
return v + 'letter'
},
link: (v) => {
return v + 'nr'
}
}
const compose = (fns) => /// compose now takes an array of functions
fns.reduce(
(prevFn, nextFn) => (...args) => nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(props.map(p => mapp[p]));
console.log(res('test'))
I do not see how you are using the object with the functions and you are not returning correctly in a few places. It should look something like
const props = ['link', 'letter'];
const mapp = {
letter: (v) => v + 'letter',
link: (v) => v + 'nr',
}
const compose = (obj, fns) => (...orgArgs) =>
fns.reduce((args, fnName) => obj[fnName].apply(obj, [args]), orgArgs);
const res = compose(mapp, props);
console.log(res('test'))

Return a modified new object from a function

what is the best practice to modify and return a new object from a function?
I wrote the following function :
export const addItemToCart = (currentCart, item) => {
const { name, ...otherProps } = item;
//if item exist in the cart
if (currentCart[name]) {
currentCart[name]["quantity"]++;
return currentCart;
}
//if the item does not exist
else
{
currentCart[name] = { ...otherProps };
currentCart[name]["quantity"] = 1;
return currentCart;
}
// the function must return a new modified object on each call
};
Obviously, the hard-coded property "quantity", and the return statements can definitely be improved.
how can I improve this function to be more readable?
More "readable" is very opinion-based, either way, you can try something like this:
const currentCart = {
hello: {
quantity: 1
}
};
const addItemToCart = (currentCart, item) => {
const { name } = item;
// Short circuit + return the last value
const quantityPrev = currentCart[name] && currentCart[name].quantity;
// Or operator on boolean expression
const quantity = 1 + (quantityPrev || 0);
// Destructing for shallow copy, dynamic key assign
return { ...currentCart, [name]: { quantity } };
};
console.log(addItemToCart(currentCart, { name: 'hello' }));
console.log(addItemToCart(currentCart, { name: 'blazer' }));

Chaining and Function Composition

Here is a piece of code I am trying to understand:
const seq2 = (f1, f2) => {
return (...args) => {
return f2( f1( ...args) );
}
}
const seq = ( f1, ...fRest) =>
fRest.reduce( seq2, f1 );
const elevator = {
floor: 5
};
const up = elevator => {
return {
floor: elevator.floor + 1
}
};
const down = elevator => {
return {
floor: elevator.floor - 1
}
};
const move = seq( up, up, down, up);
const newElevator = move( elevator );
console.log( newElevator.floor ) // shows 7
this is an example from js functional programming course. And I am trying to figure out if I could simplify seq function so it would look like this
const seq = ( ...fRest) =>
fRest.reduce( seq2 );
????
Is there any specific reason why I have to pass f1 as a first argument and then pass it further to the reduce method as an initialValue ?? When I won't specify the initialValue in the reduce method wouldn't it treat the first array element - accumulator - as an initialValue by default? I will much appreciate if someone could explain to me what is going on in that code :)
seq() would attempt to reduce an empty array without an accumulator, which is an error – not that f1 fixes this the way the author has it written here.
initialValue – Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used. Calling reduce() on an empty array without an initial value is an error – source MDN: Array.prototype.reduce
A more robust implementation of seq will not cause an error when seq() is used to build an empty sequence
const identity = x =>
x
const seq2 = (f, g) =>
(...args) => f (g (...args))
const seq = (...fs) =>
fs.reduce (seq2, identity)
const up = elevator =>
({ floor: elevator.floor + 1 })
const down = elevator =>
({ floor: elevator.floor - 1 })
console.log
( seq (up, up, down, up) ({ floor: 3 }) // { floor: 5 }
, seq () ({ floor: 3 }) // { floor: 3 }
)
A simplified version of seq that promotes better functional hygiene by disallowing composition of variadic functions
const identity = x =>
x
const seq = (f = identity, ...rest) =>
f === identity
? f
: x => seq (...rest) (f (x))
const up = elevator =>
({ floor: elevator.floor + 1 })
const down = elevator =>
({ floor: elevator.floor - 1 })
console.log
( seq (up, up, down, up) ({ floor: 3 }) // { floor: 5 }
, seq () ({ floor: 3 }) // { floor: 3 }
)

React: .map is not a function

So I am fairly new to react and most of my learning have been by watching tutorial, So at this point, I diverted from my instructor and started implementing it using my own understanding and then I was thrown with following error
React: .map is not a function
Here is the code
render() {
let person = null;
if (this.state.showPerson) {
person= (
<div>
{
this.state.person.map((el, index) => {
return <Person
key={el.id}
click={this.deletePersonHandler.bind(index)}
name={el.name}
age={el.age}
changed={(event) => this.eventSwitchHandler(event, el.id)} />
})
}
</div>
);
}
return (
The error occured after I implement eventSwitchHandler, Here is my switch handler code
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
console.log("newP")
const newP = {...personInput}
console.log(newP)
newP.name = event.target.value
console.log(personInput)
this.setState ({person: newP})
}
[Updated] Here is State
state = {
person: [
{id: "name1n", name: "Rohit", age: 24},
{id: "name2l", name: "Hariom", age: 23},
{id: "name3g", name: "Vaibhav", age: 58}
],
someOtherState: "Untouched state",
showPerson: false
}
[Update] Here is my instructor code, His name change handler is equal to my eventSwitchHandler
Again, My first question would be why does .map is not a function error occurs and while console.logging stuff, I observed something which is kinda rare to me for which I have attached a screenshot (why does the name appear to be different in both the places?)
Your person appears to be a javascript object and not an array which is what provides the map function.
You can check out the rest of the details in the docs here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
To iterate the Object by .map method, utilizing the Object.keys() which returns an array of a given object's keys:
Object.keys(this.state.person).map((key, index) => {
console.log(this.state.person[key]);
})
Update
You have done different things to your instructor code:
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newP = {...personInput} // **** newP is an object. ****
newP.name = event.target.value
// What you missed:
// let person = this.state.person;
// person[personInput] = newP;
// this.setState ({person: person});
this.setState ({person: newP}) // **** now person becomes object, not an array any more. ****
}
You are not updating the state correctly in eventSwitchHandler
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
console.log("newP")
const newP = {...personInput} // newP is an object here
console.log(newP)
newP.name = event.target.value
console.log(personInput)
this.setState ({person: newP}) // overwriting person array with object
}
You would change that to
eventSwitchHandler = (event, id) => {
const personInputIndex = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newName = event.target.value
this.setState (prevState => ({
person: [
...prevState.person.slice(0, personInputIndex),
{...prevState.person[personInputIndex], name: newName},
...prevState.person.slice(personInputIndex)
]
})
)
}
or
eventSwitchHandler = (event, id) => {
const personInputIndex = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newName = event.target.value
this.setState (prevState => ({
person: Object.assign([], prevState.person, {
[personInputIndex]: {...prevState.person[personInputIndex], newName
}})
})
)
}
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id;
}
const person = {...this.state.person[personInput]};
person.name = e.target.value;
const newPerson =[...this.state.person];
newPerson[personInput] = person;
this.setState ({person: newPerson})
}

Categories

Resources