On page load I am calling a metadata API which returns an object with the metadata type as the key name and an array of metadata objects as the value like so:
The metadata objects need to be simplified and altered to play nicely with my React input components (each object will be a separate checkbox or radio button with a value and label). I basically need to return the same format but with value and label keys instead of id, display_on, name etc. I am doing the following in my container component to alter the data ready for being passed via props to my form components. The issue is that metadataFormattedForInput always return an empty object.
function mapStateToProps(state, ownProps) {
const metadataFormattedForInput = {};
Object.keys(state.metadata).forEach((key) => {
const metadataOptions = [];
state.metadata[key].map(option => (
metadataOptions.push({ value: option.id, label: option.name })
));
return (
Object.assign({}, metadataFormattedForInput, {
[key]: metadataOptions,
})
);
});
return {
metadataOptions: metadataFormattedForInput,
};
}
The culprit is the way you're using Object.assign (you're adding the properties to an empty object that you just discard afterwards because it's returned in a forEach call). However, Object.assign is unnecessary:
function mapStateToProps(state, ownProps) {
const metadataFormattedForInput = {};
Object.keys(state.metadata).forEach(key => {
metadataFormattedForInput[key] = state.metadata[key].map(option =>
({ value: option.id, label: option.name })
);
});
return {
metadataOptions: metadataFormattedForInput,
};
}
const metadata = {
phaseofthegame: [{id: 1, name: 'foo'}],
theme: [{id: 2, name: 'bar'}]
};
console.log(
mapStateToProps({ metadata }).metadataOptions
);
If you want to keep using Object.assign, then you can combine it with Array.reduce:
function mapStateToProps(state, ownProps) {
const renameProps = (acc, key) => Object.assign(acc, {
[key]: state.metadata[key].map(option =>
({ value: option.id, label: option.name })
)
});
const metadataOptions =
Object.keys(state.metadata).reduce(renameProps, {});
return { metadataOptions };
}
Here are some of the issue with your code:
You are using forEach in the wrong way, forEach don't return any value. In case you want to return some value, use map.
Also, you are using map and using it like forEach i.e. you are not utilizing the returned value
function mapStateToProps(state, ownProps) {
let metadataFormattedForInput = Object.keys(state.metadata).reduce((metadataFormattedForInput, key) => {
let metadataOptions = state.metadata[key].map(option =>
({ value: option.id, label: option.name })
);
metadataFormattedForInput[key] = metadataOptions;
return metadataFormattedForInput;
},{});
return {
metadataOptions: metadataFormattedForInput,
};
}
As others have said, and some other issues:
Returning a value in a forEach callback has no effect
On the other hand, a map callback should have a returned value
Object.assign does not mutate the second argument, so metadataFormattedForInput is not modified at all
Here is a functional way to do this, moving the Object.assign call outside the map and building the output with the spread syntax:
function mapStateToProps(state, ownProps) {
return {
metadataOptions: Object.assign(...Object.keys(state.metadata).map(key => (
{ [key]: state.metadata[key].map(option => (
{ value: option.id, label: option.name }
))
}
)))
};
}
var state = {
metadata: {
phaseofthegame: [
{ id: 123, display_on: ['a', 'b'], name: 'Attacking' },
{ id: 456, display_on: ['c', 'd'], name: 'Transition to Attack' },
],
theme: [
{ id: 789, display_on: ['e', 'f'], name: 'Rondo' },
{ id: 101, display_on: ['g', 'h'], name: 'Defending Individually' },
]
}
};
state = mapStateToProps(state);
console.log(JSON.stringify(state, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I am trying to implement a MultipleSelectList from this library react-native-dropdown-select-list. And I am saving the selected items in the AsyncStorage of #react-native-async-storage/async-storage. I implemented 2 useState variables: first const [selected, setSelected] = useState([{}]); this is a list of {name : muscleGroup, value : id(s) of the exercise(s)} (this is the object that is saved in the list of viewDataList [see setExerciseViewLists ]). The other useState variable that I have implemented is:
const [selectedCount, setSelectedCount] = useState({ // a list of the number of selected exercises per muscleGroup
Back: 0,
Legs: 0,
Chest: 0,
.....
});
The idea: when I call handleSelect (see handleSelect) from my MultipleSelectList I give it 2 parameters (val and item) val should be the id(s) of the exercise(s) because I defined it as this in my MultipleSelectList (see MultipleSelectList) and the item is one list item from viewDataList.
The problem is: val is for some reason, not an ID or a list of IDs, it is an anonymous function:
function (val) {
var temp = _babel_runtime_helpers_toConsumableArray__WEBPACK_IMPORTED_MODULE_0___default()(new
Set([].concat(_babel_runtime_helpers_toConsumableArray__WEBPACK_IMPORTED_MODULE_0___default()(val),
[value])));
return temp;
}
(I don't really understand what this is). Any help would be appriciated.
MultipleSelectList
{viewDataList.map((item, index) => (
<MultipleSelectList
data={item.list.map(listItem => ({
value: listItem.id, //here
}))}
save="value"
setSelected={(val) => handleSelect(val, item)}
selected={selected.map(item => item.value)}
/>
))}
handleSelect/Async saving
const handleSelect = async (val, item) => {
setSelectedCount(prevState => ({
...prevState,
[item.name]: prevState[item.name] + 1, //item.name = muscleGroup
}));
setSelected(prevState => ([
...prevState,
{
name: item.name,
value: val //val should be the ID(s)
},
]));
try {
await AsyncStorage.setItem('selected', JSON.stringify([
...selected,
{
name: item.name,
value: val,
}
]));
await AsyncStorage.setItem('selectedCount', JSON.stringify({
...selectedCount,
[item.name]: selectedCount[item.name] + 1,
}));
} catch (e) {
console.e('Error saving data to AsyncStorage:', e);
}
};
setExerciseViewLists
const setExerciseViewLists = () => {
let list = [];
list.push(
{.....},
{
num: 3,
name: "muscleGroup",
list: [{.....}, { id: "123", exercises : "somthing" }, {.....}]
},
{.....},
);
setViewDataList(list);
};
The issue is not with your data but with the setSelected prop for which you are sending a custom function handleSelect. But if you see the library's documentation, it is clearly mentioned like setSelected is for For Setting the option value which will be stored in your local state. So you can only store the selected values in the local state. The code should be like below inside the component.
const [selectedValues, setSelectedValues] = useState();
return <MultipleSelectList
data={SOME_DATA}
save="value"
setSelected={setSelectedValues}
/>
I'm trying to create a filter function that receives two arguments: an array (which is going to be filtered) and an object which is the filter criteria.
Consider the array structure as:
const items = [
{
category: "Social",
areasAffected: ["Area_01", "Area_02"],
},
{
category: "Environmental",
areasAffected: ["Area_01", "Area_02", "Area_03", "Area_04"],
}
];
and, consider the object as below:
const filters = {
category: [{ value: "Social", label: "Social" }],
areasAffected: [{ value: "Area_01", label: "Area_01" }]
}
and I have defined the filter function as:
const filterChallenges = (items, filters) => {
const filterKeys = Object.keys(filters);
return items.filter((item) => {
filterKeys.forEach((key) => {
if (!Array.isArray(item[key])) {
return item[key]
.toString()
.toLowerCase()
.includes(
filters[key].map((filterEle) =>
filterEle.value.toString().toLowerCase()
)
);
} else {
return item[key].map((arrEle) => {
arrEle
.toString()
.toLowerCase()
.includes(
filters[key].map((filterEle) =>
filterEle.value.toString().toLowerCase()
)
);
});
}
});
});
};
when I run the function, it returns an empty array. Does anybody have any suggestion?
The code itself has two issues. First, forEach doesn't return a value. You'll want to use Array.every() here if you want to require ALL of the filters to match. Otherwise you'll want to use Array.some().
Second, String.includes() doesn't take an array, but a string. Here, you'll want to use Array.every() again if you want ALL of the items of the filter array to match the value, otherwise use Array.some(). As for the else branch which handles item values which are arrays, you'll want to use Array.some() if you just want ONE of the array values to match.
const items = [
{
category: "Social",
areasAffected: ["Area_01", "Area_02"],
},
{
category: "Environmental",
areasAffected: ["Area_01", "Area_02", "Area_03", "Area_04"],
},
];
const filterChallenges = (items, filters) => {
const filterKeys = Object.keys(filters);
return items.filter((item) => {
// ALL of the filters must be matched (.every())
return filterKeys.every((filterKey) => {
if (!Array.isArray(item[filterKey])) {
const candidateValue = item[filterKey].toString().toLowerCase();
// ALL of the filter values must be included in the item value
// (.every())
return filters[filterKey].every((filterEle) =>
candidateValue.includes(filterEle.value.toString().toLowerCase())
);
}
// Value is an array, ONE element (.some()) must match ALL of the
// filter values (.every())
return item[filterKey].some((candidate) => {
const candidateValue = candidate.toString().toLowerCase();
return filters[filterKey].every((filterEle) =>
candidateValue.includes(filterEle.value.toString().toLowerCase())
);
});
});
});
};
const ret = filterChallenges(items, {
category: [{ value: "Social", label: "Social" }],
areasAffected: [{ value: "Area_01", label: "Area_01" }],
});
console.log(ret);
Additionally, if all of your values are strings, .toString() is redundant.
I'm trying to create new object with different properties name from Array.
Array is:
profiles: Array(1)
0:
column:
name: "profileName"
title: "Profile name"
status: "Active"
I want to create new function that return object with two properties:
id: 'profileName',
profileStatus: 'Active'
The function that I have create is returning only one property as undefined undefined=undefined.
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.reduce((obj, profile) => {
console.log('profiles', profile);
return ({
...obj,
id: profile.column.name,
profileStatus: profile.status,
});
}, {});
}
The function getProfile is taking as input array 'profiles' from outside,
I've just tested here and this seems to be working actually
const getProfile1 = (p) => p.reduce((obj, profile) =>({
...obj,
id: profile.column.name,
profileStatus: profile.status,
}), {});
You can use map as an alternative.
var profiles = [{"column":{"name": "profileName3","title": "3Profile name"},"status": "Active"},{"column":{"name": "profileName","title": "Profile name"},"status": "Active"}];
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.map(function(profile,v){
return {id:profile.column.name,profileStatus: profile.status};
});
}
console.log(getProfile(profiles));
Whenever I use reduce in this way, I usually index the final object by some sort of an id. As noted in another answer, you could use map in this situation as well. If you really want your final data structure to be an object, however, you could do something like this:
/**
* returns object indexed by profile id
*/
const formatProfiles = (profiles) => {
return profiles.reduce((obj, profile) => {
return {
...obj,
[profile.id]: {
id: profile.column.name,
profileStatus: profile.status,
}
};
}, {});
};
const profiles = [
{
id: 0,
status: 'active',
column: {
name: "profile_name_1",
title: "profile_title_1",
},
},
{
id: 1,
status: 'inactive',
column: {
name: "profile_name_2",
title: "profile_title_2",
}
}
];
const result = formatProfiles(profiles);
/**
* Result would look like this:
*/
// {
// '0': { id: 'profile_name_1', profileStatus: 'active' },
// '1': { id: 'profile_name_2', profileStatus: 'inactive' }
// }
I would like to map one array of object into another in a more functional style, I am using typescript.
Basically I am using delete to remove a property on a object, I would like to know if there is a better way to write it.
const data = props.data.map(d => ({
order: d.position,
logs: d.batches.map(b => {
let log= {
amount: b.scrap,
batchNumber: '', // NO GOOD
}
if (!b.batch || b.batch.length === 0) {
delete log.batchNumber // NO GOOD
}
return log
}),
}))
example input data:
const data = [
position: 1,
batches: [
{batchNumber: '', ammount: 3}
]
]
result:
const data = [{
order: 1,
logs:[ {ammount:3}]
}
]
You can do another map on the batches to return a new array of objects, and attach that to your returned object instead:
const out = data.map(({ position: order, batches }) => {
const logs = batches.map(({ batchNumber, ammount }) => {
if (batchNumber) return { batchNumber, ammount };
return { ammount };
});
return { order, logs }
});
DEMO
One approach would be to make a shallow copy of the target omitting keys you want to delete, for example:
let drop = key => obj => Object.keys(obj).reduce((r, k) =>
k === key ? r : {...r, [k]: obj[k]}, {});
let test = [
{foo:11, bar:2, baz: 3},
{foo:22, bar:2, baz: 3},
{foo:33, bar:2, baz: 3},
];
console.log(test.map(drop('bar')));
To add another option to the mix: it is possible to use Object.assign to optionally assign the property:
const data = [{
position: 1,
batches: [{batchNumber: '',ammount: 3}, {batchNumber: 'withNr',ammount: 4}]
}];
const res = data.map(d =>
({
order: d.position,
logs : d.batches.map(({ammount, batchNumber}) => Object.assign({ammount}, batchNumber ? {batchNumber} : null ))
})
);
console.log(res);
constructor (props) {
super(props)
this.state = { items: this.props.items }
// items props is: [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}]
}
onClick () {
const myArray = this.state.items
const ids = ['45', '73']
ids.forEach((id, index) => {
myArray.find(x => x.id === id).foo = index
})
}
I need to change foo value to index value. So the result should look like
myArray = [{'id':'73','foo': 1},{'id':'45','foo': 0}]
I think with this, I do get the current value, but the syntax is wrong to change its value:
myArray.find(x => x.id === '45').foo = 'new'
I do get the error Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
You can use map to change the property you want:
const myArray = [{ id: '73', foo: 'bar' }, { id: '45', foo: 'new' }, { id: '46', foo: 'do not change me' }]
const ids = ['45', '73']
const newArr = myArray.map(item => {
if (ids.indexOf(item.id) !== -1) {
return {
...item,
foo: 'newFooValue'
}
}
return item
})
console.log(newArr)
It's also important to note that change object properties directly (such as myArray.find(x => x.id === '45').foo) is not a good practice though.
In case itemsis in you state, you can simple change it by:
this.setState(prevState => ({
items: prevState.items.map(/* same code as in the snippet above */),
}))