Related
I'm having some issues populating data from an API. When I console.log the state "dataFromApi", it's working just fine. Meaning, I'm getting an arr of multiple objects.
However, I then plugged in the API data from the state into the "columnsFromBackend", "items" section. When I then console.log the "columns" state at the bottom of the page which is just all the data from "columnsFromBackend", it returns me all the hardCodedData but not the one from the API.
Meaning, I'm just receiving an empty array. This is the output from the console.log(columns). Any suggestions on what might be happening here?
const [dataFromApi, setDataFromApi] = useState([]);
useEffect(() => {
getLeadsClApproved().then((resp) => {
setDataFromApi(resp.data);
});
}, []);
const hardCodedData = [
{
id: uuid(),
business_name: "Canva",
first_name: "Melanie",
last_name: "Perkins",
created_at: "15th of Nov., 2022",
},
{
id: uuid(),
business_name: "Microsoft",
first_name: "Bill",
last_name: "Gates",
created_at: "15th of Nov., 2022",
},
];
const columnsFromBackend = {
[uuid()]: {
name: "In Progress",
items: hardCodedData,
},
[uuid()]: {
name: "CL Approved",
items: dataFromApi,
},
[uuid()]: {
name: "CL Declined",
items: [],
},
[uuid()]: {
name: "Awaiting Response",
items: [],
},
[uuid()]: {
name: "Interview Scheduled",
items: [],
},
[uuid()]: {
name: "Accepted",
items: [],
},
[uuid()]: {
name: "Rejected",
items: [],
},
};
const [columns, setColumns] = useState(columnsFromBackend);
console.log(columns); // logs the columns with its content
The columns state variable is initialized with the value of columnsFromBackend on the initial render. On subsequent renders (like when the API data returns, resulting in the effect calling setDataFromApi), columnsFromBackend is updated with the new value of dataFromApi, but the state value columns is not. This is how useState works. The argument passed to useState provides an initial value, but changes to that initialization value do not automatically flow through. The only way the state value gets updated, columns in this case, is by calling setColumns. You could add another effect to synchronize columns with dataFromApi, but I'd propose something like this instead (I'm assuming you want the uuid's you're generating to be stable across renders):
const [dataFromApi, setDataFromApi] = useState([]);
const [uuids] = useState(() => ({
canvaUuid: uuid(),
microsoftUuid: uuid(),
inProgressUuid: uuid(),
clApproveUuid: uuid(),
clDeclinedUuid: uuid(),
awaitingResponseUuid: uuid(),
interviewScheduledUuid: uuid(),
acceptedUuid: uuid(),
rejectedUuid: uuid(),
}));
useEffect(() => {
getLeadsClApproved().then((resp) => {
setDataFromApi(resp.data);
});
}, []);
const columns = React.useMemo(() => {
const hardCodedData = [
{
id: uuids.canvaUuid,
business_name: "Canva",
first_name: "Melanie",
last_name: "Perkins",
created_at: "15th of Nov., 2022",
},
{
id: uuids.microsoftUuid,
business_name: "Microsoft",
first_name: "Bill",
last_name: "Gates",
created_at: "15th of Nov., 2022",
},
];
return {
[uuids.inProgressUuid]: {
name: "In Progress",
items: hardCodedData,
},
[uuids.clApproveUuid]: {
name: "CL Approved",
items: dataFromApi,
},
[uuids.clDeclinedUuid]: {
name: "CL Declined",
items: [],
},
[uuids.awaitingResponseUuid]: {
name: "Awaiting Response",
items: [],
},
[uuids.interviewScheduledUuid]: {
name: "Interview Scheduled",
items: [],
},
[uuids.acceptedUuid]: {
name: "Accepted",
items: [],
},
[uuids.rejectedUuid]: {
name: "Rejected",
items: [],
},
};
}, [uuids, dataFromApi]);
console.log(columns); // logs the columns with its content
Using useMemo here instead of useState guarantees that columns will always be in sync with dataFromApi.
I have an array containing objects:
let sportTag = [
{ id: 1, name: 'FOOTBALL', found: false },
{ id: 2, name: 'TENNIS' , found: false },
{ id: 3, name: 'BASKET' , found: false },
]
I have also have another array containing objects and for every object a field (sports) that is an array:
let person = [{
id: 1,
name: "Mark",
age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2,
name: "Rupert",
age: 40,
sports: ["golf"],
}, {
id: 3,
name: "John",
age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}]
I would like to change sportTag found field to true when sportTag name is equal to every person sport.
I tried with a nested map
const result = sportTag.map(st => {
person.map(p => {
p.sports.map(s => {
if (st.name.toLocaleUpperCase() === s.toLocaleUpperCase()) {
return {
...st, found: true
}
}
return s
})
return p
})
return st
})
console.log(sportTag)
//OUTPUT
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS' , found: false },
// { id: 3, name: 'BASKET' , found: false }
console.log(result)
//OUTPUT
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS' , found: false },
// { id: 3, name: 'BASKET' , found: false }
Why are the changes not reflected in the result? I expect the output to be:
{ id: 1, name: 'FOOTBALL', found: true },
{ id: 2, name: 'TENNIS' , found: true },
{ id: 3, name: 'BASKET' , found: false }
The problem with your code is that you are always returning st for each iteration of the first map, so you get the original values.
You probably want something like this:
const result = sportTag.map(st => {
const foundSport = person.find(p =>
p.sports.find(s => st.name.toLocaleUpperCase() === s.toLocaleUpperCase())
);
return foundSport
? { ...st, found: true }
: st;
});
console.log(sportTag)
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS', found: false },
// { id: 3, name: 'BASKET', found: false }
console.log(result)
// { id: 1, name: 'FOOTBALL', found: true },
// { id: 2, name: 'TENNIS', found: true },
// { id: 3, name: 'BASKET', found: false }
From the above comment ...
The OP already mentions in the description of the problem the correct way of achieving what the OP wants ... "I would like to change [the] sportTag's found field to true when [the] sportTag's name [value] is equal to every [any/some] person's sport [item]." ... thus the OP does not need to implement a nested, twice map but a map/some task.
But (especially for a bigger amount of data) instead of following the above suggested approach which within every map iteration additionally iterates again with every nested some task, one could choose a lookup based approach which works with a Map instance. The mapping task itself will be very simple. For the latter one could even choose an implementation which makes the mapping agnostic to the current lookup's variable/constant name since one would provide its reference as the map method's 2nd thisArg parameter.
One of cause could implement the lookup creation with less iteration cycles. But since it is done once it will never become the performance bottleneck.
function createLookupOfAnyPracticedSport(persons) {
return new Map(
// (5) create a map as lookup for unique sport items.
Array
// (3) create array from set of step (2)
.from(
new Set(
// (2) create a set of unique sport
// items/values as of step (1)
persons
// (1) concatenate array of all `sports`
// practiced by any person.
.reduce((result, { sports }) =>
result.concat(sports), []
)
)
)
// (4) sanitize and map the unique sport items/values
// in order to qualify as entries for step (5) ...
.map(sport => [sport.toLocaleUpperCase(), true])
);
}
function createUpToDateSportTagFromBoundSports(tagItem) {
const allSportsLookup = this;
// create (updated) shallow copy of the original
// sport tag item in order to not directly mutate
// such an item's original reference.
return {
...tagItem,
found: allSportsLookup
.has(tagItem.name.toLocaleUpperCase())
};
}
const personList = [{
id: 1, name: "Mark", age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2, name: "Rupert", age: 40,
sports: ["golf"],
}, {
id: 3, name: "John", age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}];
const sportTagList = [{
id: 1, name: 'FOOTBALL', found: false,
}, {
id: 2, name: 'TENNIS', found: false,
}, {
id: 3, name: 'BASKET', found: false,
}];
const mappedTagList = sportTagList
.map(
createUpToDateSportTagFromBoundSports,
createLookupOfAnyPracticedSport(personList),
);
console.log({
mappedTagList,
sportTagList,
personList,
});
console.log(
'entries of any practiced sport ...',
[...createLookupOfAnyPracticedSport(personList).entries()],
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
let person = [{
id: 1, name: "Mark", age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2, name: "Rupert", age: 40,
sports: ["golf"],
}, {
id: 3, name: "John", age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}];
let sportTag = [{
id: 1, name: 'FOOTBALL', found: false,
}, {
id: 2, name: 'TENNIS', found: false,
}, {
id: 3, name: 'BASKET', found: false,
}];
sportTag.forEach((elem, index, array) => {
person.forEach((el, i, arr) => {
if (person[i].sports.indexOf(sportTag[index].name.toLocaleLowerCase()) != -1) {
sportTag[index].found = true;
}
});
});
console.log(sportTag);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I am trying to improve the time complexity and quality of the code snippet below.
I am iterating through one array to check if the element this array exists in the object, should this be true it should return the name matching the element id in the object.
how can I do this without having a nested loop?
Can someone tell me what I can do to make this algo better, please?
Thank you all in advance.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const getGenreName = () => {
let result = [];
for (let genre of data.genres) {
//console.log("genre", genre.name)
for (let id of genres) {
//console.log('id',genres[i])
if (id === genre.id) result.push(genre.name);
}
}
console.log(result);
};
getGenreName();
You can use reduce and includes as others have already shown. This will make the code a bit cleaner, but not change the overall runtime complexity. To improve runtime complexity you may need to use a different data structure.
For instance instead of
let genres = [1,2,3,4];
as a simple array, you could use a Set, which has a better lookup performance.
let genres = new Set([1,2,3,4]);
Then you can use this as follows
let result = data.genres
.filter(g => genres.has(g.id))
.map(g => g.name);
and won't need any explict for loops
The simplest improvement would probably be converting genres to a Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
and use the has method to check if each id in the data is a member of the set of chosen genres.
You can also convert the data to a map with the ids as the keys in order to look up by id quickly instead of looping, but that is only faster if the data is reused many times.
JavaScript #reduce in the example outlined below would have O(n) time complexity. This only loops through the array once. We could use filter, and map but it would result in us having to loop through the array twice.
const getGenreName = () => {
const genreSet = new Set(genres);
return data.genres.reduce((accumulator, { id, name }) => {
if (genreSet.has(id)) accumulator.push(name);
return accumulator;
}, []);
};
console.log(getGenreName()); // [ 'Action', 'Adventure', 'Science Fiction' ]
We are initializing the reducer to start with the array [], or an empty array, and then checking to see if the genre property of the object is included in the genres array, if it isn't, return the accumulator, if it is, append it to the end of the accumulator and return it.
You wanted this in one loop, so here it is:
let result = [];
data.genres.forEach(function (e) {
if (genres.includes(e.id)) result.push(e.name);
});
console.log(result);
In case you were wondering about forEach, here's a very good reference: https://www.w3schools.com/jsref/jsref_foreach.asp
The current time complexity is O(MN) where M is the length of data.genres and N is the length of genres.
Time complexity in JavaScript depends on which engine you use, but in most cases you can use a Map to reduce this time complexity to O(max{N,M}):
const getGenreName = () => {
const dataGenresMap = new Map( // O(M)
data.genres.map(({id,...params}) => [id,params]) // O(M)
)
let result = []
for (let id of genres) { // O(N)
if (dataGenresMap.has(id)) result.push(dataGenresMap.get(id).name) // O(1)
}
console.log(result)
}
If you might be doing this more than once then I'd recommend using a Map. By creating a hash map, retrieving genre names per id is much more performant.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const genreById = new Map ();
data.genres.forEach(({id, name}) => genreById.set(id, name));
const pushMapValueIfTruthy = map => array => key => {
const val = map.get(key);
if (val) {
array.push(val);
}
};
/** function that takes an array, then id, and pushes corresponding name (if exists) into the array. */
const pushGenreNaneIfExists = pushMapValueIfTruthy(genreById);
const getGenreNames = (ids) => {
result = [];
ids.forEach(pushGenreNaneIfExists(result));
return result;
};
console.log(getGenreNames(genres));
I have an array of objects and Im trying to filter by matching ids
//Vehicle is added to quote
function filterByID(item) {
return item.id === 1;
}
this.vehicle = this.data.filter(filterByID);
data is as follows:
data: [
0: {
id: 0,
name: name
},
1: {
id: 1,
name: name
},
2: {
id: 2,
name: name
}
]
Im getting an empty error when I check the vehicle part
Are you using it like this:
const data = [
{
id: 0,
name: '',
},
{
id: 1,
name: '',
},
{
id: 2,
name: '',
},
];
function filterByID(item) {
return item.id === 1;
}
console.log(data.filter(filterByID)); // output: [{ "id": 1, "name": "" }]
You don't always need to define a separate function, you can use an arrow function, as below.
const data = [{
id: 0,
name: name
},
{
id: 1,
name: name
},
{
id: 2,
name: name
}
]
const vehicle = data.filter(item => item.id === 1);
console.log(vehicle);
This works fine in pure JS, it looks like it might be an issue with the lifecycle or state of your application. Use console.log to make sure that this.data is what you expect it to be
I started experimenting with functional programming lately and I'm trying to convert an old module I have written using imperative programming.
Let's say I have two arrays of objects i.e
orders: [
{
idOrder: 1,
amount: 100,
customerId: 25,
},
{
idOrder: 2,
amount: 200,
customerId: 20,
}
]
customers: [
{
customerId: 20,
name: "John Doe",
orders: []
},
{
customerId: 25,
name: "Mary Jane",
orders: []
}
]
I want to push all the orders to their respective customer. Is there a clean way of doing it?
I have tried this , but obviously it doesn't work that way :
customers.orders = orders.filter((x) => {
if (x.customerId === customerId) {
customer.orders.push(x);
}
});
Thanks
You could use a Map and get all customers first and then push the orders to the customers.
var object = { orders: [{ idOrder: 1, amount: 100, customerId: 25 }, { idOrder: 2, amount: 200, customerId: 20 }], customers: [{ customerId: 20, name: "John Doe", orders: [] }, { customerId: 25, name: "Mary Jane", orders: [] }] },
map = object.customers.reduce((m, a) => m.set(a.customerId, a), new Map);
object.orders.forEach(a => map.get(a.customerId).orders.push(a));
console.log(object.customers);
Possible solution:
for (c of customers){
c.orders.push(orders.filter( function(o){ return o.customerId === c.customerId} ));
}
If you think of customers as your accumulator you can Reduce orders with customers as your initial value.
NOTE: this does mutate customers if you do not want this as a side-effect you would have to clone customers. Also there is not error handling for customerId not found.
var orders = [{ idOrder: 1, amount: 100, customerId: 25 }, { idOrder: 2, amount: 200, customerId: 20}];
var customers = [{ customerId: 20, name: "John Doe", orders: [] }, { customerId: 25, name: "Mary Jane", orders: [] } ];
var customers_orders = orders.reduce(
(accum, v) =>
{ accum.find(
c => c.customerId == v.customerId).orders.push(v);
return accum;
}, customers);
console.log(customers_orders);
You can write a function and pass it to reduce method and compose it with map
Just one things: once it's created, it may never change. You can user Object.assign and concat.
var customersWithOrders = customers.map(function(customer) {
var relatedOrders = orders.filter(function(order) { return order.customerId === customer.customerId })
return Object.assign(
customer,
{
orders: customer.orders.concat(relatedOrders)
}
)
})