React Filtering Data with Hooks - arrays

I'm working with a dataset and want to implement a filtering option to only display what is selected (as filters do :) ). Here's the data and my code so far:
// Movies data =
[
{
name: "a",
genre: "comedy",
year: "2019",
},
{
name: "b",
genre: "drama",
year: "2019",
},
{
name: "c",
genre: "suspense",
year: "2020",
},
{
name: "d",
genre: "comedy",
year: "2020",
},
{
name: "e",
genre: "drama",
year: "2021",
},
{
name: "f",
genre: "action",
year: "2021",
},
{
name: "g",
genre: "action",
year: "2022",
},
]
and in my code, I'm have a piece of state for the API response (all data) as well as filtered data per year
import { useEffect, useState, useMemo } from 'react';
const MovieData = () => {
const [movies, setMovies] = useState([]); // all data. This will not change after API call
const [results setResults] = useState([]); // this will change based on selection
const [year, setYear] = useState({
y2019: false,
y2020: false,
y2021: false
});
// making API call
useEffect(() => {
fetch("myapiep")
.then(response => response.json())
.then(data => setMovies(data))
}, []);
// get subsets of data
const {m2019, m2020, m2021} = useMemo(() => {
const m2019 = movies.filter(m => m.year === '2019');
const m2020 = movies.filter(m => m.year === '2020');
const m2021 = movies.filter(m => m.year === '2021');
return {m2019, m2020, m2021}
});
// So far so good. Now this is where things get tricky for me
// I want to, based on the selection, modify my results array
useEffect(() => {
// update results based on movie year selected
if (year.y2019) setResults([...results, ...m2019]);
// HELP: filter out results when year is unselected
// this is not working
else {
const newArr = results.filter((movie) => !m2019.includes(movie));
}
if (year.y2020) setResults([...results, ...m2020]);
else {
const newArr = results.filter((movie) => !m2020.includes(movie));
}
if (year.y2021) setResults([...results, ...m2021]);
else {
const newArr = results.filter((movie) => !m2021.includes(movie));
}
// if none are selected, just return all movies
if (!year.y2019 && !year.y2020 && !year.y2021) {
setResults(movies);
}
}, [year]);
// I'm suppressing the logic to toggle years (y20xx) true/false for simplicity, but can add it if folks judge necessary
return (
<div>
{results.map((movie) => (
<Movie
key={uuidv4()}
name={movie.name}
genre={movie.genre}
year={movie.year}
/>
))}
</div>
)
}
What works: set filter works, for instance, setting the filter to movies made in 2019 returns
[
{
name: "a",
genre: "comedy",
year: "2019",
},
{
name: "b",
genre: "drama",
year: "2019",
},
]
What doesn't: unset the filter.

The below code snippet achieves the necessary logic (sans the React Hooks, API, and other items):
const movies = [{
name: "a",
genre: "comedy",
year: "2019",
},
{
name: "b",
genre: "drama",
year: "2019",
},
{
name: "c",
genre: "suspense",
year: "2020",
},
{
name: "d",
genre: "comedy",
year: "2020",
},
{
name: "e",
genre: "drama",
year: "2021",
},
{
name: "f",
genre: "action",
year: "2021",
},
{
name: "g",
genre: "action",
year: "2022",
}
];
const filterMyMovies = (byField = 'year', value = '2020', myMovies = movies) => (
(myMovies || [])
.filter(mov => !byField || !mov[byField] || mov[byField] === value)
);
console.log('movies in year 2020:\n', filterMyMovies());
console.log('movies in year 2019:\n', filterMyMovies('year', '2019'));
console.log('movies in genre drama:\n', filterMyMovies('genre', 'drama'));
console.log('movies with name d:\n', filterMyMovies('name', 'd'));
console.log('movies with NO FILTER:\n', filterMyMovies(null, null));
Explanation
Fairly-straightforward filtering of an array of objects, with only 1 column & 1 value. May be improvised for multi-column & multi-value filtering.
How to use this
The way to use this within React-Hooks is to invoke the 'filterMyMovies' method with the appropriate parameters when the filter-criteria changes. Suppose, the year is set to 'no-filter', then simply make call to the 'filterMyMovies' method with the first & second params as null.
Suppose we need to filter by year and then by genre as well, try something like this:
filterMyMovies('genre', 'comedy', filterMyMovies('year', '2019'));
This will return 2019 comedy movies.

Related

Error when changing the value of an object within an array causing another object to be called

I'm working on a project where I need the values in an array to change depending on if a button is clicked for that specific item. Right now, I'm able to get the first name (pescatarian) in my first subCategories object to work, but when I press the second one (vegan), it is displaying my second subCategories object and changing data[0] to
"category": "Diet", "subCategories": [{"name": "one", "value": false}, {"name":"two", "value": false}, {"name": "three", "value": false}, {"name": "four", "value": false}]}
from:
{"category": "Diet", "subCategories": [{"name": "pescatarian", "value": false}, {"name": "vegan", "value": false}, {"name": "vegetarian", "value": false}]}
From there, I am able to press the first two subCategories (one and two), but if I press the third one I get an error stating: TypeError: undefined is not an object (evaluating 'data[index].subCategories'). Does anyone know why this is happening? I would really appreciate any help or advice. Thank you!
const [data, setData] = useState([
{
category: 'Diet',
subCategories: [
{name:'pescatarian', value: false },
{name:'vegan', value: false },
{name:'vegetarian', value: false }
],
}, {
category: 'Daily Exercise in Hours',
subCategories: [
{name:'one', value: false },
{name:'two', value: false },
{name:'three', value: false },
{name:'four', value: false }
],
},
])
const onChangeValue = (item, index, newValue) => {
console.log('data[index]:', data[index].subCategories)
const newSub = data[index].subCategories.map(subCat => {
if (subCat.name.includes(item.name)) {
return {
...subCat,
value: newValue,
}
}
return subCat
})
const newData = data.map(newItem => {
if (newItem.subCategories.some(x => x.name === item.name)){
return {
...newItem,
subCategories:newSub,
}
}
return newItem
})
setData(newData)
}
onValueChange={newValue => onChangeValue(item, index, newValue)}

How to filter an item from an array in state in React functional component (To Do List)

This is my array where I add array object
const [task, setTask] = useState([]);
export const productArray = [
{
Name: "White Flowers",
oldPrice: "$45.99",
newPrice: "$35.99",
},
{
Name: "Blue and Pink Jeans",
oldPrice: "$57.99",
newPrice: "$40.99",
},
{
Name: "Yellow Tshirt",
oldPrice: "$53.99",
newPrice: "$37.99",
},
{
Name: "Black Hoodie",
oldPrice: "$40.99",
newPrice: "$33.99",
},
]
How do I filter this array of objects? I don't want to filter them by their attributes. I want to filter using their indexes.

React: How to render a list of items grouped by category (using .map)

I have an array (myArray), stored like so (in firebase):
[
{
"id": "1",
"Category": "Hardware",
"Name": "Xtreme"
},
{
"id": "123",
"Category": "Software",
"Name": "Obsolete"
},
{
"id": "12345",
"Category": "Software",
"Name": "V1"
},
{
"id": "1234567",
"Category": "Hardware",
"Name": "CPU"
}
]
I am using the following code:
const sorterAR = [];
myArray.forEach((item) => {
let cat = sorterAR.find(
(cat) => cat.id === item.id
);
if (!cat) {
cat = {
id: item.id,
Category: item.Category,
items: [],
};
sorterAR.push(cat);
}
cat.items.push(item);
});
And then displaying like so:
<div className="App">
{sorterAR.map((cat) => (
<>
<div>
<b>{cat.Category}</b>
</div>
<ul>
{cat.items.map((item) => (
<li>{item.Name}</li>
))}
</ul>
</>
))}
</div>
This works in that it produces an output like:
**Hardware**
Xtreme
**Hardware**
CPU
**Software**
Obsolete
**Software**
V1
How do I alter this to produce the following output:
**Hardware**
Xtreme
CPU
**Software**
Obsolete
V1
So that it displays the category name and then all the items in that category, and then moves to the next one and so forth?
I assumed that order doesn't matter if Hardware or Software should come first.
First I categorized the array into an object of Category objects using Array.prototype.reduce().
From the resultant object you can build the JSX
var data1 = [
{
id: '1',
Category: 'Hardware',
Name: 'Xtreme',
},
{
id: '123',
Category: 'Software',
Name: 'Obsolete',
},
{
id: '12345',
Category: 'Software',
Name: 'V1',
},
{
id: '1234567',
Category: 'Hardware',
Name: 'CPU',
},
];
const categorizedData = data1.reduce((acc, curr) => {
const { id, Category, Name } = curr;
if (!acc[Category]) {
acc[Category] = {
items: [],
};
}
acc[Category].items.push(Name);
return acc;
}, {});
console.log(categorizedData);
Object.keys(categorizedData).map((key, index) => {
console.log(`Category: ${key}`);
categorizedData[key].items.map((item, index) =>
console.log(`Item ${index}: ${item}`)
);
});

Remove duplication of a specific attribute of an object inside an array - Angular

Consider the following array:
packages = [
{modelName: "flatRate", name:"Enterprise", price: "$150"},
{modelName: "flatRate", name:"Gold", price: "$190"},
{modelName: "usageBased", name:"Enterprise", price: "$50"},
{modelName: "userBased", name:"Extreme", price: "$50"},
]
What I want to achieve is:
Use the packages.modelName as a heading and list all its items under it without repeating modelName. For Example:
flatRate
Enterprise ($150) , Gold ($190)
usageBased
Enterprise ($50)
userBased
Extreme ($50)
Notice how 2 objects having modelName: "flatRate" are shown under one heading.
What I've tried:
Take the packages array use a foreach loop on it and remake the array as:
packagesResult = [
{ modelname: "flatRate",
model: [ {name: "Enterprise", price: "$150"} ]
},
{ modelname: "flatRate",
model: [ {name: "Gold", price: "$190"} ]
},
{ modelname: "usageBased",
model: [ {name: "Enterprise", price: "$50"} ]
},
{ modelname: "userBased",
model: [ {name: "Extreme", price: "$50"} ]
},
]
and then tried to use a filter on it using observable but didn't work. Any help will be highly appreciated. Thanks.
Would a reduce like so fit your purpose?
models = packages.reduce((models, package) => {
models[package.name] = package.price;
return models;
}, {})
I would opt for something like the following:
myPackages = {};
function consolidatePayload() {
for(let package of packages){
if(this.myPackages.hasOwnProperty(package.modelName)) {
this.myPackages[package.modelName][package.name] = package.price;
} else {
this.myPackages[package.modelName]= {[package.name]: package.price};
}
}
console.log(this.myPackages);
}
IT gives an and result along the lines of
{
flatRate: {
Enterprise: "$150",
Gold: "$190"
},
usageBased: {
Enterprise: "$50"
},
userBased: {
Extreme: "$50"
}
}
EDIT: reduce inspiration, per #Śaeun acreáť
function consolidatePayload() {
let models = this.packages.reduce((models, package) => {
if(models.hasOwnProperty(package.modelName)) {
models[package.modelName][package.name]= package.price;
} else {
models[package.modelName] = {[package.name]: package.price}
}
return models;
}, {})
}
You can create array of price types for each model name like below ;)
const result = packages = [{
modelName: "flatRate",
name: "Enterprise",
price: "$150"
},
{
modelName: "flatRate",
name: "Gold",
price: "$190"
},
{
modelName: "usageBased",
name: "Enterprise",
price: "$50"
},
{
modelName: "userBased",
name: "Extreme",
price: "$50"
},
].reduce( (acc, item, i) => {
if( i === 0 ) {
acc.push(modifiedObject(item));
return acc;
}
let foundItem = acc.find(it => it.modelName === item.modelName);
if (foundItem) {
addNewPriceType(foundItem, item);
return acc;
} else {
acc.push(modifiedObject(item));
return acc;
}
}, [] );
function modifiedObject(item) {
return {
modelName : item.modelName,
model : [
{ name : item.name, price : item.price }
]
};
}
function addNewPriceType(foundItem, item) {
foundItem.model.push({
name : item.name,
price : item.price
});
}
console.log(result);

How to update the value in action creator - react

i am trying to update my response in the action creator.
Once then i receive response i am updating the time zone(as of now hardcoded)
Here the response
data = [
{
"created": {timestamp: "2018-05-12T16:55:32Z", Id: "234j", name: "jim"}
"id": "804690986026920900000061579629"
"lastUpdated": {timestamp: "2018-05-12T16:55:32Z", Id: "234j", name: "jim"}
"note": "standard 9"
},
{
"created": {timestamp: "2018-05-12T17:49:32Z", Id: "444a", name: "antony"}
"id": "804690986026920900000061579630"
"lastUpdated": {timestamp: "2020-05-12T16:49:32Z", Id: "444a", name: "antony"}
"note": "standard 9"
},
{
"created": {timestamp: "2018-05-12T17:55:12Z", Id: "123m", name: "mark"}
"id": "804690986026920900000061579631"
"lastUpdated": {timestamp: "2020-05-12T17:49:12Z", Id: "123m", name: "mark"}
"note": "standard 9"
}
];
action.js
then((results) => {
const hardcodedValue = "2020-05-22T04:49:44Z"
const getLocaltime = results.data.map((updatetime)=>{
return {...updatetime, lastUpdated.timestamp:hardcodedValue}
//getting error at lastUpdated.timestamp
})
results.data = getLocaltime;
dispatch({
type: "RECEIVED_DATA",
payload: updateId === '' ? {} : results,
})
Thats not a valid object:
{ ...updatetime, lastUpdated.timestamp:hardcodedValue }
Try fixing it to:
{ ...updatetime, lastUpdated: { ...updatetime.lastUpdated, timestamp: hardcodedValue } }
You could also do it like this:
updatetime.lastUpdated.timestamp = hardcodedValue;
return {...updatetime}
This would update the lastUpdated object and since you return a new outer object, the reference would change and you would not lose any data.
The easiest way, since its new data and the object reference can stay the same, you can just mutate it like this:
then((results) => {
const hardcodedValue = "2020-05-22T04:49:44Z";
results.data.forEach((row) => {
row.lastUpdated.timestamp = hardcodedValue;
});
dispatch({
type: "RECEIVED_DATA",
payload: localAccountId === '' ? {} : results,
})

Resources