How to delete nested state object in immutable-js inside reducer? - javascript

I want to delete an entity from data (which is List inside object someData). I am using fromJS of immutable js in my reducer to keep the state immutable
I tried using updateIn, deleteIn, update, removeIn and whatever I could find on immutable-js. But it didn't work for me. Most probably I am using these functions the wrong way.
import { fromJS, updateIn } from 'immutable';
import * as type from './constants';
export const initialState = fromJS({
someData: [],
loading: true,
});
function someReducer(state = initialState, action) {
switch (action.type) {
case type.DELETE_SINGLE_ENTITY:
updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
return state;
default:
return state;
}
}
export default someReducer;
//example someData
/*
{
date: "",
data: [
{
"id": "1",
"machine_type": "xyz",
"created_time": "2019-06-18T10:36:60Z",
...
},
{
"id": "22",
"machine_type": "abc",
"created_time": "2019-06-20T10:36:60Z",
...
},
{
"id": "2",
"machine_type": "kjhkh",
"created_time": "2019-06-11T12:36:60Z",
...
}
]
}
*/
I want to delete an entity matching with the id passed in action.
Before deleting the output of state.get('someData') is in the above example. My expected output (when action.id is 2) when I type state.get should be:
{
date: "",
data: [
{
"id": "1",
"machine_type": "xyz",
"created_time": "2019-06-18T10:36:60Z",
...
},
{
"id": "22",
"machine_type": "abc",
"created_time": "2019-06-20T10:36:60Z",
...
}
]
}

Finally! Got it!
this:
return updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
instead of this:
updateIn(state, ['someData', 'data'], val =>
val.filter(x => x.id !== action.id),
);
return state;
Previously I thought updateIn would update the state itself but it doesn't it returns the updated object. So just returning the updateIn would be fine.

You can do it using the filter function
const sampleData = [{id: 1},{id: 2},{id: 3}]
const idToRemove = 2;
const updatedData = sampleData.filter(el => {
return el.id !== idToRemove
})
console.log(updatedData);

Related

How to store multiple values in to objects within an array

Problem:
I have an api and each object within the api doesn't have a value. I would like to add a unique value to each object within the array so that I can create a function and use 'e.target.value' with event listeners. I'm doing this in nextjs.
Why:
Because I want to store each value in to an array and localstorage before eventually displaying the data that was stored in the array as like a favorites item.
Is there a way of doing this ?
Information:
data.items has over 30+ objects -
"items": [
{
"id": 119603782,
"node_id": "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
"name": "react-contextual",
"full_name": "drcmda/react-contextual",
"private": false,
"owner": {
"login": "drcmda",
"id": 2223602,
}
{
"id": 119603782,
"node_id": "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
"name": "react-contextual",
"full_name": "drcmda/react-contextual",
"private": false,
"owner": {
"login": "drcmda",
"id": 2223602,
}
So far my data has been sorted and mapped like so.
{data.items
.sort(function (a, b) {
return b.stargazers_count - a.stargazers_count && new Date (b.created_at) - new Date(a.created_at)
})
.map((d) => (
<div onClick={checkId} key={d.id} className=" border-white p-5">
<h1 className="text-2xl font-bold">Repo name: {d.name}</h1>
An example using index as unique for displaying purpose.
const data = {
items: [
{
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
},
{
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
},
],
};
const items = data.items.map((item, idx) => ({ ...item, idx }));
// new item
const newItem = {
id: 119603782,
node_id: "MDEwOlJlcG9zaXRvcnkxMTk2MDM3ODI=",
name: "react-contextual",
full_name: "drcmda/react-contextual",
private: false,
owner: {
login: "drcmda",
id: 2223602,
},
};
function addNewItems(items, newItem) {
newItem.idx = items.length;
items.push(newItem);
return items;
}
console.log(addNewItems(items, newItem));
If you want add a VALUE key to each object you can do the following:
const [isLoading, setIsLoading] = useState(false),
[items, setItems] = useState([]);
useEffect(() => {
(async () => {
setIsLoading(true);
try {
//you can replace axios with fetch
const res = await axios('https://your-api'),
// clone deep is from lodash => you can use the spread (...) operator if you want
clonedItems = cloneDeep(res.data.items);
clonedItems.forEach((el) => {
// add whatever you want in the (value)
el.value = 'required value';
});
//sort items based on the required key
clonedItems.sort((a, b) => {
//replace name with your key
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
});
//check the modified items
console.log(clonedItems);
setItems(clonedItems);
} catch (err) {
//replace it with your error handler code
console.log(err);
} finally {
setIsLoading(false);
}
})();
}, []);
Notes:
You should sort your elements before storing it in the state
You can replace axios with fetch
you can use the spread operator (...) in place of cloneDeep from lodash

Get all children from parent JSON data in React

This is an example of my json data
[
{
"Id":"114",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"115",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
"Id":"44",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"45",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"50",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"52",
"Padre":"Telefonia",
"Hijo":"Configuracion"
},
{
"Id":"70",
"Padre":"Telefonia",
"Hijo":"Rutas"
}
]
So far I have achieved the following data in console.log:
(3) [{…}, {…}, {…}]
0: {Padre: "CRM", Hijo: "Argumentarios", Description: "SALUD NORMAL", Id: "114"}
1: {Padre: "Permisos", Hijo: "root", Description: "Usuarios", Id: "44"}
2: {Padre: "Telefonia", Hijo: "Audio", Description: "Locuciones", Id: "50"}
I need to show all the children of each parent element.
I am creating a menu and I want the submenu associated with each parent to appear. I would like the children not to appear repeated. In my json parent it's Padre and Child is Hijo (is in spanish).
This is my original code:
componentWillMount(){
fetch('fake-son.php')
.then(response => response.json())
.then(menuSubmenu =>{
const result = [];
const map = new Map();
for (const item of menuSubmenu) {
if(!map.has(item.Padre)){
map.set(item.Padre, true); // set any value to Map
result.push({
Padre: item.Padre,
Hijo: item.Hijo,
Description: item.Description,
Id:item.Id
});
}
}
this.setState({
menuSubmenu:this.state.menuSubmenu.concat(result)
})
console.log(result);
})
}
Can you help me show all the children about their father? Thanks a lot
You can use array.reduce to create a relation like so,
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
parentList: []
};
}
componentDidMount() {
//call your rest api here...
const list = [{
"Id": "114",
"Padre": "CRM",
"Hijo": "Argumentarios"
},
{
"Id": "115",
"Padre": "CRM",
"Hijo": "Argumentarios"
},
{
"Id": "44",
"Padre": "Permisos",
"Hijo": "root"
},
{
"Id": "45",
"Padre": "Permisos",
"Hijo": "root"
},
{
"Id": "50",
"Padre": "Telefonia",
"Hijo": "Audio"
},
{
"Id": "52",
"Padre": "Telefonia",
"Hijo": "Configuracion"
},
{
"Id": "70",
"Padre": "Telefonia",
"Hijo": "Rutas"
}
];
const PadreMap = list.reduce((acc, obj) => {
if (!acc[obj.Padre]) {
acc[obj.Padre] = {
...obj,
Hijo: [obj.Hijo]
};
} else {
!acc[obj.Padre].Hijo.includes(obj.Hijo) && acc[obj.Padre].Hijo.push(obj.Hijo)
}
return acc;
}, {});
this.setState({parentList: Object.keys(PadreMap).map((padre) => ({
name: padre,
children: PadreMap[padre].Hijo
}))})
}
render() {
return <div >{
this.state.parentList.map(parent => <ul>{parent.name}:
{parent.children.map(hijo => <li>{hijo}</li>)}
</ul>)
}< /div>
}
}
ReactDOM.render( < Demo / > , document.getElementById('app'));
li{
margin-left: 30px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Yes you can show it by the command object.toSource() it show all the object attribute...
Use the following chunk in place of your map code
menuSubMenu.forEach((item)=>{
if(item){
const key = item.Padre;
if(!map.get(key)){
map.set(key,[item]);
}else{
map.get(key).push(item); // if you need just child name then push item.Hijo
}
} });
This code will give you a unique parent map as shown below
For iterating it as array and concat the results
let results = Array.from(map);// in results [0] index will have name of parent [1] index will have the array of values
You can do that simply like:
let data=[
{
"Id":"114",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"115",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"44",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"45",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"50",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"52",
"Padre":"Telefonia",
"Hijo":"Configuracion"
},
{
"Id":"70",
"Padre":"Telefonia",
"Hijo":"Rutas"
}
]
const mapChildren=()=>{
const newData=data.reduce(function (r, a) {
a=Object.assign({},a)
r[a.Padre] = r[a.Padre] || [];
r[a.Padre].push(a);
delete a.Padre;
return r;
}, Object.create(null));
return newData;
}
console.log('#Children:',mapChildren(),typeof(mapChildren()))
console.log('#PArents:',Object.keys(mapChildren()))

How to change the state object when one of the fields is an array?

I have a state tree of this form:
const initialState = {
total: 0,
discount: 0,
typeDiscount: 0,
products: data
};
In which products field is an array and the array is like this:
[{
"id":9090,
"name":"Item1",
"price":200,
"discount":10,
"type":"fiction",
"quantity": 1,
"img_url":"https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
},
{
"id":9091,
"name":"Item2",
"price":250,
"discount":15,
"type":"literature",
"quantity": 1,
"img_url":"https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
}]
Now I'm trying to change the quantity in that array, I'm new to Redux so please guide me on how to do this?
And here is my reducer:
case types.ADD_ITEM_CART:
let product_add = state.products
for (let i = 0; i < product_add.length; i++) {
if (product_add[i].id === action.id) {
product_add[i].quantity = product_add[i].quantity + 1
break
}
}
return dotProp.set(state, `products`, product_add);
You can use Array.prototype.map and iterate over the items and update the item that matches the id from the payload.
Something like that:
const state = [{
"id": 9090,
"name": "Item1",
"price": 200,
"discount": 10,
"type": "fiction",
"quantity": 1,
"img_url": "https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
},
{
"id": 9091,
"name": "Item2",
"price": 250,
"discount": 15,
"type": "literature",
"quantity": 1,
"img_url": "https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
}
];
const payload = {
id: 9091
};
const nextState = state.map(product => {
if (product.id !== payload.id) {
// not our product, return as is
return product;
}
return {
...product,
quantity: product.quantity + 1
}
});
console.log(nextState);
.as-console-wrapper {
max-height: 100% !important;
}
For removal of items you can just use Array.prototype.filter:
const state = [{
"id": 9090,
"name": "Item1",
"price": 200,
"discount": 10,
"type": "fiction",
"quantity": 1,
"img_url": "https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
},
{
"id": 9091,
"name": "Item2",
"price": 250,
"discount": 15,
"type": "literature",
"quantity": 1,
"img_url": "https://store.lexisnexis.com.au/__data/media/catalog/thumb//placeholder.jpg"
}
];
const payload = {
id: 9091
};
const nextState = state.filter(product => product.id !== payload.id);
console.log(nextState);
With the spread operator ... and array.map() you can create a new products array where all the products that do not match the id will keep the same object reference while you create a new product object for you product of interest:
case types.ADD_ITEM_CART:
const updatedProducts = state.products.map(product => {
if (product.id === action.id) {
const newQuantity = product.quantity + 1;
return { ...product, quantity: newQuantity };
}
return product;
})
return { ...state, products: updatedProducts };
}
Edit: Remove can be easily implemented with the array.filter() method, which will create a new array only containing the items that matches your predicate function; in this case all the products that does not match the id of the product you want to remove:
case types.REMOVE_ITEM_CART:
const updatedProducts = state.products.filter(product => product.id !== action.id)
return { ...state, products: updatedProducts };
}
Do not mutate your state like that for Redux or React itself. Copying objects do not create different objects. If you change a property for the new one, you mutate the original one also.
Generally, we use Object.assign or spread syntax combining with methods like .map, .filter. Object.assign or spread syntax does not create totally different objects, too. They create shallow copies, this means one level copy. If you change a nested property for the new object then again you mutate the original one. So, combine all these tools.
case types.ADD_ITEM_CART: {
// We are mapping our related array.
const newProducts = state.products.map( el => {
// If id does not match, return the element without doing nothing.
if ( el.id !== action.id ) { return el };
// id match, increment the quantity.
return { ...el, quantity: el.quantity + 1 };
})
// Lastly, return our state again without mutating it.
return { ...state, products: newProducts };
}
If we want to remove an item from an array we generally use .filter method for this.
const newProducts = state.products.filter( el => el.id !== action.id );
return { ...state, products: newProducts };

ReactJS - Search/Iterate through array of objects

I have an object like that:
{
"data": [
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Bobby"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
]
}
React code
import React from 'react';
import { Input } from 'antd';
import { connect } from 'dva';
const Search = Input.Search;
#connect(({ rule, loading }) => ({
rule,
loading: loading.models.rule,
}))
export default class SearchBox extends React.Component {
constructor(props) {
super(props)
this.state = {
isListLoaded: false,
resultArr: {}
}
}
performSearch(value) {
for( var i = this.props.rule.data.list.length; i--; ) {
for (var key in this.props.rule.data.list[i]) {
this.setState({resultArr: this.state.resultArr.push(i)});
}
}
}
componentDidMount() {
if (!this.state.isListLoaded) {
const { dispatch } = this.props;
dispatch({
type: 'rule/fetch'
});
this.setState({ isListLoaded: true });
}
}
render() {
return (
<div>
<Search
placeholder="Search..."
onChange={(event) => this.performSearch(event.target.value)}
style={{ width: "250px", "margin-left": "20px"}}
/>
</div>
);
}
}
My goal is very simple: I want to search through this object, and
return the entire array(s) that contains the keyword.
Example: if I search "Sar", I should get 2 objects:
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
Problem is, I get an error when I'm trying this code. I did search for previous solutions to this problem here on SO, but I can only find examples where there's only one element returned. What I want, is to get ALL the results that contain the keyword in ANY attributes (in this example, I'm returning 2 elements, not just one)
Any idea?
const { data } = {
"data": [
{
"id": "1234",
"is_deleted": false,
"name": "Sarah"
},
{
"id": "3520",
"is_deleted": true,
"name": "Bobby"
},
{
"id": "3520",
"is_deleted": true,
"name": "Sartah"
}
]
};
const keyword = "Sar";
const filtered = data.filter(entry => Object.values(entry).some(val => typeof val === "string" && val.includes(keyword)));
console.log(filtered);
It filters the entries of data Array with the following criterium: at least one of the entry's values must contain a given keyword.
Since IE doesn't yet support Object.values() and String.prototype.includes() you can use the following:
const containsKeyword = val => typeof val === "string" && val.indexOf(keyword) !== -1;
const filtered = data.filter(entry => Object.keys(entry).map(key => entry[key]).some(containsKeyword));
or polyfill these ES6 features, see more here.
To make the keyword lookup case insensitive, you can use RegExp:
const re = new RegExp(keyword, 'i');
const filtered = data.filter(entry => Object.values(entry).some(val => typeof val === "string" && val.match(re)));
Instead of looping through array simply use filter method of javascript
performSearch(value) {
const unfilteredData = this.props.rule.data.list;
const filteredDate = unfilteredData.filter((val) => {
return val.name.indexOf(val) !== -1;
});
this.setState({
resultArr: filteredDate,
})
}
performSearch(value) {
let filteredData = this.props.rule.data.list.filter(item => {
let isFiltered = false;
for(let key in item){
if(item[key].includes(value)){
isFiltered = true;
}
}
return isFiltered;
})
this.setState({resultArr: filteredData});
}

Appending to an array access by a key in Redux Reducer

I'm trying to add to my state within a reducer. I need to access a key, and then add to an array within that key. This is what my state currently looks like:
{teamMembers: {
PersonId1: [{ object1 }, {object2 }]
PersonId2: [{ object3 }, { object4 }]
PersonId3: [{ object5 }, { object6 }]
}}
I need to access a PersonId, based on what the action inputs, and then append an item from the action to the array. Ideally it would also create a new key and array if the key PersonId didn't already exist.
In your reducer, just do a little data manipulation. Remember to only manipulate a copy of your state and not your actual state.
const action = {
payload: {
personId: 'PersonId4',
item: {}
}
}
const state = {
PersonId1: [{ object1 }, {object2 }]
PersonId2: [{ object3 }, { object4 }]
PersonId3: [{ object5 }, { object6 }]
}
const {personId, item} = action.payload
let newState = {...state}
if (personId in newState) {
newState[personId].push(item)
} else {
newState[personId] = [item]
}
return newState
You can conditionally check for the existence of the key and then add it to an already existing array or create a new one.
Something like this:
const newItems = state[id] ? [...state[id], ...items] : items;
//...
return {
...state,
[id]: newItems
}
Note that i'm using the object spread syntax which is a proposal (in stage 3) and you need the babel plugin babel-plugin-transform-object-rest-spread to support it (or babel stage 3 preset).
Running example:
const initialState = {
teamMembers: {
PersonId1: [{
name: 'object1'
}, {
name: 'object2'
}],
PersonId2: [{
name: 'object3'
}, {
name: 'object4'
}],
PersonId3: [{
name: 'object5'
}, {
name: 'object6'
}]
}
};
const actionOne = {
type: 'ADD',
payload: {
id: 'PersonId2',
items: [{
name: 'object444'
}]
}
}
const actionTwo = {
type: 'ADD',
payload: {
id: 'PersonId777',
items: [{
name: 'object777'
}]
}
}
const reducer = (state = {}, action) => {
switch (action.type) {
case 'ADD':
{
const {
id,
items
} = action.payload;
const newItems = state[id] ? [...state[id], ...items] : items;
return {
...state,
[id]: newItems
}
}
default:
return state;
}
}
const reducerResultOne = reducer(initialState.teamMembers, actionOne);
console.log('reducerResultOne', reducerResultOne);
const reducerResultTwo = reducer(initialState.teamMembers, actionTwo);
console.log('reducerResultTwo', reducerResultTwo);

Categories

Resources