I have two arrays of objects.
const details = [
{
ciphertext: 1234,
buyer: {
op_timezone: 7689,
op_city: 'Name1',
},
assignment_info: {
info: {
end_data: 1456,
start_date: 2389,
}
}
},
{
ciphertext: 5678,
buyer: {
op_timezone: 4568,
op_city: 'Name2',
},
assignment_info: {
info: {
end_data: 3467,
start_date: 8753,
}
}
},
];
const jobIds = [
{
id: 1,
},
{
id: 2,
},
];
I need to combine two arrays and take the assignment_info.info and buyer fields from each object.
function getDetailsBuyersWithJobIds(jobIds, details) {
return jobIds.map((item, index) => ({
...item,
...details[index].buyer,
}));
};
function getDetailsAssignmentInfosWithJobIds(jobIds, details) {
return jobIds.map((item, index) => ({
...item,
...details[index].assignment_info.info,
}));
};
The question is, how can two functions be combined into one?
That there would be no duplicate function, since they perform the same thing.
You can do a generic mapping function and pass it a getter function that will be able to fetch the proper data, not sure it will help the global readibility though.
What do you think about that?
const genericMapper = (getter) => (item, index) => ({
...item,
...getter(details[index]),
});
function getDetailsBuyersWithJobIds(jobIds, details) {
return jobIds.map(genericMapper(it => it.buyer));
};
function getDetailsAssignmentInfosWithJobIds(jobIds, details) {
return jobIds.map(genericMapper(it => it.assignment_info.info));
};
const details = [
{
ciphertext: 1234,
buyer: {
op_timezone: 7689,
op_city: 'Name1',
},
assignment_info: {
info: {
end_data: 1456,
start_date: 2389,
}
}
},
{
ciphertext: 5678,
buyer: {
op_timezone: 4568,
op_city: 'Name2',
},
assignment_info: {
info: {
end_data: 3467,
start_date: 8753,
}
}
},
];
const jobIds = [
{
id: 1,
},
{
id: 2,
},
];
console.log(getDetailsBuyersWithJobIds(jobIds, details));
console.log(getDetailsAssignmentInfosWithJobIds(jobIds, details));
You can add values on return object based on condition something like this
const details = [{ciphertext: 1234,buyer: {op_timezone: 7689,op_city: 'Name1',},assignment_info: {info: {end_data: 1456,start_date: 2389,}}},{ciphertext: 5678,buyer: {op_timezone: 4568,op_city: 'Name2',},assignment_info: {info: {end_data: 3467,start_date: 8753,}}},];
const jobIds = [{id: 1,},{id: 2,},];
function getDetails(jobIds, details, props = {
getBuyer: true
}) {
return jobIds.map((item, index) => ({
...item,
...(props.getBuyer && { ...details[index].buyer
}),
...(props.getAssignment && { ...details[index].assignment_info.info
})
}));
};
console.log(getDetails([1], details, {
getBuyer: true
}))
console.log(getDetails([1], details, {
getAssignment: true
}))
Here props = { getBuyer: true} used to set a default value.
Related
Tying to change key values inside an object but its adding double values or its adding all values at once. Every name must get an value which in this case is a language slug. Not sure what I am doing wrong.
// the data
const routesObj = [
{ name: 'dashboard.index' },
{ name: 'settings.index' },
{ name: 'settings.general' },
{ ... }
]
// end results (how i want it to be)
[
{
nl: {
routes: [
{
name: 'nl.dashboard.index'
},
{
name: 'nl.dashboard.index'
},
{
name: 'nl.settings.general'
}
]
}
},
{
en: {
routes: [
{
name: 'en.dashboard.index'
},
{
name: 'en.dashboard.index'
},
{
name: 'en.settings.general'
}
]
}
}
]
// how its working now(not good)
[
{
nl: {
routes: [
{
name: 'en.nl.dashboard.index'//adding both languages
},
...
]
}
},
...
]
const routeBuilder = (routes, languages) => {
let newRoutes = []
languages.forEach(function(lang){
Object.keys(routes).forEach(function(key){
routes[key]['name'] = lang+'.'+routes[key]['name']
});
newRoutes[lang] = {routes};
});
return newRoutes
}
routeBuilder(routesObj, ['nl','en'])
The problem is here:
Object.keys(routes).forEach(function(key){
routes[key]['name'] = lang+'.'+routes[key]['name']
});
newRoutes[lang] = {routes};
routes is a reference to routesObj, so the code above modifies the original object in each languages.forEach iteration. The solution is to clone routes so that each iteration has a unique copy.
newRoutes is an array, but it's being used like an object in newRoutes[lang]. To insert an object into the newRoutes array, use Array.prototype.push.
const routesObj = [
{ name: 'dashboard.index' },
{ name: 'settings.index' },
{ name: 'settings.general' },
]
const routeBuilder = (routes, languages) => {
const newRoutes = []
languages.forEach(function(lang) {
routes = routesObj.map(x => ({...x})); // 1️⃣
Object.keys(routes).forEach(function(key){
routes[key]['name'] = lang+'.'+routes[key]['name']
});
newRoutes.push({ [lang]: { routes } }); // 2️⃣
})
return newRoutes;
}
const routes = routeBuilder(routesObj, ['nl','en'])
console.log(routes)
Alternatively, use nested Array.prototoype.maps:
const routesObj = [
{ name: 'dashboard.index' },
{ name: 'settings.index' },
{ name: 'settings.general' },
]
const routeBuilder = (routes, languages) => {
return languages.map(lang => {
return {
[lang]: {
routes: routes.map(route => ({ ...route, name: lang + '.' + route.name })),
}
}
})
}
const routes = routeBuilder(routesObj, ['nl','en'])
console.log(routes)
I have a state and within that state there is a list with several objects. What I basically want to do is take an index from this array, in this case [0], and change its state. So, in short, I want to take {id: "1", value: "world1"}, and change the id to 'something'. I made a code but it didn't come out as I expected.
this.state = {
myState: [
{ id: "1", value:"world1" },
{ id: "2", value:"world2" },
{ id: "3", value:"world3" },
]
}
const result = this.setState(prevState => ({
myState: {
...prevState[0].id,
id: 'something'
}
}))
console.log(result)
Not sure why you need this, but the issue I'm seeing here is that myState is initially an array, and then you're passing it back as an object.
Try this:
const result = this.setState(prevState => {
prevState.myState[0].id = 'something';
return prevState;
})
const state = {
myState: [{
id: "1",
value: "world1"
},
{
id: "2",
value: "world2"
},
{
id: "3",
value: "world3"
},
]
}
const setState = (({
myState
}) => {
const newState = {
myState: [...myState]
}
const array = newState.myState
const targetIndex = array.findIndex(({
id
}) => id === '1')
const item = array[targetIndex]
array[targetIndex] = { ...item,
id: 'something'
}
return newState;
})
console.log(setState(state))
Use Array.prototype.map and object spread operator
this.setState(prevState => {
return {
myState: prevState.myState.map(
myStateItem => {
if(myStateItem.id === '1') {
return {
...myStateItem,
id: 'something'
}
}
return {
...myStateItem
}
})
};
});
Hi I have object like this:
Subject {
id:1
packages: [
{id:1, isChecked:true, ...},
{id:2, isChecked:false, ...},
{id:3, isChecked:true, themes:[
{id:1, isChecked:true},
{id:1, isChecked:false},
]
]
...
}
How can i remove all not checked items from this object please? I have to send this updated object to another component in react app.
The real tree looks like this:
Subject
|-packages
|-themes (check if checked)
|-themeParts (check if checked)
|-exercises (check if checked)
If any of child is checked it should be added to new component. So if I have Two packages and only one exercise is checked it is also checked themmeparts of this exercise, also theme of theme part and theme. Packages don't have isChecked attribute but i have to add this level to new object too if any of its child is checked.
Other example... if second package has no theme,part or exercise checked i have to remove from package level down alll...
So when i finish i need to have only Subject{} object with checked items + package of that checked items...
I hope i described it good XD....
anyway i tried something like this:
returnSelectedItems(){
console.log(this.state.data);
let newData = cloneDeep(this.state.data);
newData.packages = [];
this.state.data.packages.forEach((pckg) => {
const newPackage = {
};
pckg.themes.forEach((theme, key) => {
if(theme.isChecked){
}
});
});
console.log(newData);
console.log(newData.packages);
console.log(newData.packages[0].themes);
console.log(newData.packages[0].themes[0].themeParts);
}
But this is useless i think and i really don't know how to od it properly and ezy as it can be.. Thx for help
You can create a generic function like this. This takes an array as input and reduces it recursively for any nested array property. Destructure the object and get the array property to a rest object. If the rest object has any keys, recursively call the function to filter for the isChecked property.
This will work for any name of the array property for any number of nesting
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const output = {
id: input.id,
packages: getChecked(input.packages)
};
Here's a snippet:
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const input = {
id: 1,
packages: [
{ id: 1, isChecked: true },
{ id: 2, isChecked: false },
{ id: 3, isChecked: true, themes: [
{
id: 4, isChecked: true, themeParts: [
{ id: 5, isChecked: true, exercises: [
{ id: 7, isChecked: true },
{ id: 8, isChecked: false }
]
},
{ id: 6, isChecked: true }
]
},
{ id: 9, isChecked: false }
]
},
{ id: 10, isChecked: true },
]
};
const output = {
id: input.id,
packages: getChecked(input.packages)
};
console.log(output)
I believe this is what you want. That's parent cannot be removed if children(themes) need to be kept
var subjects = {
id: 1,
packages: [{
id: 1,
isChecked: true,
},
{
id: 2,
isChecked: false,
},
{
id: 3,
isChecked: true,
themes: [{
id: 1,
isChecked: true
},
{
id: 1,
isChecked: false
},
]
}
]
};
function purge(item) {
purgeItems(item.themes ||[]);
return !item.isChecked && (item.themes ||[]).length === 0;
}
function purgeItems(themes) {
var toremove = [];
themes.forEach(function(p, i) {
if (purge(p)) {
toremove.push(i)
}
});
while (toremove.length > 0) {
themes.splice(toremove.pop(), 1);
}
}
purgeItems(subjects.packages);
console.log(JSON.stringify(subjects));
This is what i needed. using this principe: let {packages: packages, ...newData} = tempData;
returnSelectedItems(){
let tempData = cloneDeep(this.state.data);
let {packages: packages, ...newData} = tempData;
this.state.data.packages.forEach(pckg => {
const {themes,...newPckg} = pckg;
newPckg.themes = [];
pckg.themes.forEach(theme =>{
if(!theme.isChecked){
return;
}
const {themeParts, ...newTheme} = theme;
newTheme.themeParts =[];
newPckg.themes.push(newTheme);
theme.themeParts.forEach(part =>{
if(!part.isChecked){
return;
}
const {knowledges, ...newPart} = part;
newPart.knowledges = [];
newTheme.themeParts.push(newPart);
part.knowledges.forEach(knowledge =>{
if(!knowledge.isChecked){
return;
}
newPart.knowledges.push(knowledge);
});
});
});
if(newPckg.themes.length > 0){
newData.packages = newPckg;
}
});
return newData;
}
I'm trying to update a state in my redux reducer by passing the deleted item id. The Id is located in tasks and associated column. What would a clean way to delete this item?
So far my reducer looks like this:
case DELETE_TASK:
const update = delete state.tasks[`${action.payload.id}`]
const findIdCol = ?
return {
}
const initState = {
tasks: {
"task1": {
id: "task1",
content: "hello1"
},
"task2": {
id: "task2",
content: "hello2"
},
"task3": {
id: "task2",
content: "hello3"
}
},
columns: {
"column1": {
id: "column1",
taskIds: []
},
"column2": {
id: "column2",
taskIds: []
},
"column3": {
id: "column3",
taskIds: ["task3", "task1"]
}
},
main: {
"main": {
id: "main",
taskIds: ["task2"]
}
},
columnOrder: ["column1", "column2", "column3"],
mainOrder: ["main"]
};
You can convert the columns object into its entries, allowing you to loop over each entry You can recreate the columns object, using reduce:
state.columns = Object.entries(state.columns).reduce((a, [k, v]) => {
v.taskIds = v.taskIds.filter(taskId => taskId !== action.payload.id)
a[k] = v
return a
}, {})
Demo:
const state = {
tasks: {
task1: { id: 'task1', content: 'hello1' },
task2: { id: 'task2', content: 'hello2' },
task3: { id: 'task2', content: 'hello3' }
},
columns: {
column1: { id: 'column1',taskIds: [] },
column2: { id: 'column2',taskIds: [] },
column3: { id: 'column3',taskIds: ['task3', 'task1'] }
},
main: {
main: { id: 'main', taskIds: ['task2']}
},
columnOrder: ['column1', 'column2', 'column3'],
mainOrder: ['main']
}
const id = 'task1'
state.columns = Object.entries(state.columns).reduce((a, [k, v]) => {
v.taskIds = v.taskIds.filter(taskId => taskId !== id)
a[k] = v
return a
}, {})
console.log(state)
You would replace id with action.payload.id in this case.
If you wanted to reuse this code, you could make it into a function:
const state = {
tasks: {
task1: { id: 'task1', content: 'hello1' },
task2: { id: 'task2', content: 'hello2' },
task3: { id: 'task2', content: 'hello3' }
},
columns: {
column1: { id: 'column1',taskIds: [] },
column2: { id: 'column2',taskIds: [] },
column3: { id: 'column3',taskIds: ['task2', 'task1'] }
},
main: {
main: { id: 'main', taskIds: ['task2']}
},
columnOrder: ['column1', 'column2', 'column3'],
mainOrder: ['main']
}
const id = 'task2'
const removeId = (o, id) => {
return Object.entries(o).reduce((a, [k, v]) => {
v.taskIds = v.taskIds.filter(taskId => taskId !== id)
a[k] = v
return a
}, {})
}
state.columns = removeId(state.columns, id)
state.main = removeId(state.main, id)
console.log(state)
To keep your reducer function pure and state immutable, I would not recommend to update the properties of the state directly (neither with reduce() returning an object, nor with delete operator). The cleanest approach here would be to follow the pattern
grab the state
put it into variable
modify relevant parts
replace the original state with its modified copy
That would give you something, like this (e.g. if you wish to remove task3 from your default state):
//dependencies
const { createStore } = Redux
//store initialization
const defaultState = {tasks:{"task1":{id:"task1",content:"hello1"},"task2":{id:"task2",content:"hello2"},"task3":{id:"task3",content:"hello3"}},columns:{"column1":{id:"column1",taskIds:[]},"column2":{id:"column2",taskIds:[]},"column3":{id:"column3",taskIds:["task3","task1"]}},main:{"main":{id:"main",taskIds:["task2"]}},columnOrder:["column1","column2","column3"],mainOrder:["main"]},
appReducer = (state=defaultState, action) => {
switch(action.type){
case 'DELETE_TASK': {
//destructuring state into parts to be modified
const {tasks,columns,main} = state,
{payload: taskIdToDelete} = action
//iterate through tasks to delete matching id's
Object.entries(tasks).forEach(([key, {id}]) => id == taskIdToDelete && delete tasks[key])
//iterate through columns to filter out deleted task
Object.entries(columns).forEach(([key, {taskIds}]) => columns[key]['taskIds'] = taskIds.filter(taskId => taskId != taskIdToDelete))
//filter out deleted task from main
main.main.taskIds = main.main.taskIds.filter(taskId => taskId != taskIdToDelete)
return {...state, tasks, columns, main}
}
default: return state
}
},
store = createStore(appReducer)
//dispatch 'DELETE_TASK'
store.dispatch({type:'DELETE_TASK', payload: 'task3'})
//log modified state
console.log(store.getState())
.as-console-wrapper{min-height:100%}
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script><script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script><script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.4/redux.min.js"></script><script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.1/react-redux.min.js"></script><div id="root"></div>
I have this object below. I was wondering how I can select a specific item and update a property. For example. Item 1 I want to add a task in the array.
item: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
I currently have
const getItem = {...state.items['item-1']}
const newTaskList = [...getItem.task, newTask.id]
const newState = {
...state,
items: {
...state.items,
//How do I spread new array correctly in item 1?
//...state.items['item-1'].task
}
};
You need to use the object key i.e item-1 and clone the properties for it and add the new list for the task key. In short you need to clone at each level of the object before overriding the key that you wish to update
const newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: newTaskList
}
}
};
Assuming the starting point:
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
}
};
If you want to add a task to item-1's task array without modifying things in place (which is important in React state), you have to copy state, items, item-1, and item-1's task:
let newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: [...state.items['item-1'].task, newTask]
}
}
};
Live Example:
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4']
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: []
},
}
};
let newTask = "task-4";
let newState = {
...state,
items: {
...state.items,
'item-1': {
...state.items['item-1'],
task: [...state.items['item-1'].task, newTask]
}
}
};
console.log(newState);
In lodadash you can get and set nested object from an object, here is my own implementation of it:
//helper to get prop from object
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//helper to set nested prop in object
const set = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
let state = {
items: {
'item-1': {
id: 'item-1',
title: 'To do',
task: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'item-2': {
id: 'item-2',
title: 'In progress',
task: [],
},
},
};
console.log(
set(
state,
['items','item-1','task'],
(tasks)=>tasks.concat('new task')
)
);
You can put the get and set in a library and it would make setting deeply nested values easier on the eyes of future readers of your code.