unable to update setState - reactjs

Tried to update a state. Somehow the setState isn't working..
this is the function and state:
state = {
security: {
0: { id: "54321", name: "test1"}
1: { id: "98765", name: "test2"}
}
}
removeSecInState = (security) => () => {
var temp = Object.assign({}, this.state.security)
var temp1 = Object.values(temp)
var index = temp1.findIndex(id => id.id === security.id) //getIndex
delete temp[0]
this.setState({security: temp},() => {
console.log(temp, "inside");
console.log(this.state.security, "inside1")
})
}
console.log(temp, "inside") =
security: {
1: { id: "98765", name: "test2"}
}
console.log(this.state.security, "inside1") =
security: {
0: { id: "54321", name: "test1"}
1: { id: "98765", name: "test2"}
}
somehow, state is not updated to be the same with temp, always goes back to the previous state

It seems that you have not included the this keyword before state, also you should
Try this
this.state = {
security: {
0: { id: "54321", name: "test1"}
1: { id: "98765", name: "test2"}
}
}
Also you should try to refactor the code a little bit
Maybe like this
this.state = {
security: [
{ id: "54321", name: "test1"},
{ id: "98765", name: "test2"}
]
}
removeSecInState(sec) {
this.setState({
security: this.state.security.filter(item => item.id !== sec.id)
})
}

Related

How to set state in nested array of objects in ReactJs?

I have this object as a state in reactjs. I want to add another object inside the "childoptions: []" array which is nested inside the options array on button click.
How can I achieve this, pls help...
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
// {
// id: uuid(),
// optionName: 'red 🔴',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'green 🟢',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'blue 🔵',
// value: '',
// childOptions: [],
// },
],
},
parentId: null,
});
This is achievable by copy the prevState in the new state with the new object inserted inside the options array.
A more detailed explanation could be found at https://stackoverflow.com/a/26254086/9095807
setSelect1((prevState) => {
return {
...prevState,
properties: {
label: 'Select1',
options: prevState.properties.options.map(obj => obj.id === id ? { ...obj, childOptions: [...obj.childOptions, newChildOptions] } : obj),
}
}
})

How to loop through object for finding a particular value using typescript and react?

I want to hide a button if there is atleast one order or subareas that at least one orders whose status 'ACCEPTED' or 'DONE'.
How can I hide the "Hide me" Menu item when either item has at least one area with order status 'ACCEPTED' OR 'DONE' or at least one area with subareas order with status 'ACCEPTED' or 'DONE'.
Below is the react code with the item I am trying to process
function Parent() {
const item = {
owner: {
id: '1',
cognitoId: '2',
},
areas: [{
id: '1',
orders: [
{
id: '1',
status: 'ASSIGNED',
}, {
id: '2',
status: 'ACCEPTED',
}
],
subAreas: [
{
id: '1',
orders: [
{
id: '4',
status: 'DONE',
}
],
}
]
}, {
id: '2',
orders: [{
id: '3',
status: 'ASSIGNED',
}, {
id: '4',
status: 'ACCEPTED',
}
],
subAreas: [{
id: '2',
orders: [{
id: '5',
status: 'DONE',
}, {
id: '6',
status: 'ACCEPTED',
}
],
}
]
}
]
}
return ({
item && item.owner && item.owner.cognitoId && (
< Menu > Hide me < / Menu > )
});
}
this Item is reference to how the data will look.
For additional information...Item is of type Item which is like below
export interface Item {
id: string;
name: string;
areas?: Area[];
}
export interface Area {
id: string;
Orders?: Order[];
subAreas?: Area[];
}
export interface Order {
id: string;
status: OrderStatus; //this is the one we are looping through
}
export interface OrderStatus {
NEW = 'NEW',
ASSIGNED = 'ASSIGNED',
SENT = 'SENT',
ACCEPTED = 'ACCEPTED',
REJECTED = 'REJECTED',
DONE = 'DONE',
}
what i have tried is like below
const hasDoneAccepted = () => {
return Object
.keys(item)
.some(key =>
(key === 'status' &&
['DONE', 'ACCEPTED'].indexOf(item[key]) > -1) ||
(typeof item[key] === 'object' &&
hasDoneAccepted(item[key])));
}
But this gives me an error like below,
Element implicitly has any type because expression of type "status" cant be used on index type 'Item'. property status doesnt exist on type 'Item'.
i am new to using typescript and not sure whats going wrong. could someone help me with this. thanks.
EDIT:
using the solution provided
const hasAcceptedOrDoneOrders =
item.areas?.reduce<Order[]>((acc, area) => { //error here
area.orders?.forEach(order => acc.push(order));
area.subAreas?.forEach(subArea => subArea.orders?.forEach(order =>
acc.push(order)));
return acc;
}, [])
.some(order => order.status === "ACCEPTED" || order.status === "DONE");
}
this gives me an here at line
item.areas?.reduce<Order[]>((acc, area) =>
"parsing error: expression expected"
looping through the object is always an option. I'd use a helper method too. Something like this:
hideBool = false;
for(cost area of item.areas()) {
if(hideBool) { break; }
hideBool = this.checkArray(area.orders);
if(!hideBool) {
for(const subArea of area.subAreas) {
hideBool = this.checkArray(subArea);
if(hideBool) { break; }
}
}
}
checkArray(array: any[]) {
for( const item in array) {
if(item.status === 'ACCEPTED' || item.status === 'DONE') {
return true;
}
}
return false;
}
This is how you can write the filter function:
const hasAcceptedOrDoneOrders =
item.areas?.reduce<Order[]>((acc, area) => {
area.orders?.forEach(order => acc.push(order));
area.subAreas?.forEach(subArea => subArea.orders?.forEach(order => acc.push(order)));
return acc;
}, [])
.some(order => order.status === "ACCEPTED" || order.status === "DONE");
and use it to conditional display React element:
return !hasAcceptedOrDoneOrders && <Menu>Hide me</Menu>;
However, to eliminate all the TypeScript syntax errors you need to correctly type the item object and fix the data schema:
const item: Item = {
id: "1",
name: "Item1",
owner: { id: "1", cognitoId: "2" },
areas: [
{
id: "1",
orders: [
{ id: "1", status: OrderStatus.ASSIGNED },
{ id: "2", status: OrderStatus.ACCEPTED }
],
subAreas: [
{
id: "1",
orders: [{ id: "4", status: OrderStatus.DONE }]
}
]
},
{
id: "2",
orders: [
{ id: "3", status: OrderStatus.ASSIGNED },
{ id: "4", status: OrderStatus.ACCEPTED }
],
subAreas: undefined
}
]
};
Schema:
interface Item {
id: string;
name: string;
areas?: Area[];
owner: any;
}
enum OrderStatus {
NEW = 'NEW',
ASSIGNED = 'ASSIGNED',
SENT = 'SENT',
ACCEPTED = 'ACCEPTED',
REJECTED = 'REJECTED',
DONE = 'DONE',
}

How to use spread operator to update array inside an object?

What the fetch returns is a list of items. I want to add those into state.
const [state, setState] = useState({
list: {
items: [],
}
});
fetch('http://example.com/list/')
// GET response: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }]
.then((resList) => resList.json())
.then((list) => {
list.forEach(({ name }) => {
const itemUrl = `https://example.com/list/${name}`;
fetch(itemUrl)
// GET responses:
// { name: 'foo', desc: '123' }
// { name: 'bar', desc: '456' }
// { name: 'baz', desc: '789' }
.then((itemRes) => itemRes.json())
.then((item) => {
setState((prevState) => ({
...prevState,
list: {
items: [...state.list.items, item]
},
});
})
})
}
})
console.log(state);
// result: [{ name: 'baz', desc: '789' }]
// but wanted: [{ name: 'foo', desc: '123' }, { name: 'bar', desc: '456' }, { name: 'baz', desc: '789' }]
In your case no need to use prevState in setState.
I prepared an example for you. Just be careful at using hooks.
https://codesandbox.io/s/recursing-wood-4npu1?file=/src/App.js:0-567
import React, { useState } from "react"
import "./styles.css"
export default function App() {
const [state, setState] = useState({
list: {
items: [
{ name: "foo", desc: "123" },
{ name: "bar", desc: "456" },
],
},
})
const handleClick = () => {
setState(() => ({
list: {
items: [...state.list.items, { name: "baz", desc: "789" }],
},
}))
}
return (
<div className="App">
<button onClick={handleClick}>Click Me </button>
<hr />
{JSON.stringify(state)}
</div>
)
}
You can't directly access the callback for useState hooks. This is how you can update state after fetching the data:
setState({
...state,
list: {
items:[...state.list.items, item]
},
});

can't change nested object in a reducer even when not mutating the objects and arrays

I'm trying to update a state inside a reducer, i know i shouldn't mutate the object or nested objects so I'm using map for arrays or object spread for objects. but it seems i can't really change a value that is deeply nested.
Beside the fact that i can't change the state, i really dislike how the code looks and especially the number of loops i need to do to just change one property. I feel like there is a better, readable and more performant way of doing this.
this is the state:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
this is the action i'm dispatching:
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
this is the reducer:
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const itemIndex = state.findIndex(item => item.id === action.payload.itemId);
const tagIndex = state[itemIndex].tags.findIndex(t => t.id === action.payload.tagId);
const nextTag = {
...state[itemIndex].tags[tagIndex],
name: action.payload.newTagName
};
const nextTags = [
...state[itemIndex].tags.slice(0, tagIndex),
nextTag,
...state[itemIndex].tags.slice(tagIndex + 1, ),
];
const nextItem = {
...state[itemIndex],
tags: nextTags
};
const nextState = [
...state.slice(0, itemIndex),
nextItem,
...state.slice(itemIndex + 1)
];
}
default:
return state;
}
};
Your reducer should work just fine, you just forgot to return nextState in your case block.
As for less iterations i suggests this pattern:
map over the items, if the current item's id is different from the itemId you have in the payload then return it as is.
If the item's id is the same then return a new object and then map over the tags, doing the same condition like you did with the item.
If the tag's id isn't the same as the tagId in the payload return it as is, if it does the same return a new object.
Here is a running example:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
itemId,
tagId,
newTagName
}
} = action;
const nextState = state.map(item => {
if (item.id !== itemId) return item;
return {
...item,
tags: item.tags.map(tag => {
if (tag.id !== tagId) return tag;
return {
...tag,
name: newTagName
}
})
}
});
return nextState;
}
default:
return state;
}
};
console.log(itemsReducer(items, action));
As for a more readable code, i suggest to use more reducers.
A thumb of rule i use is to create a reducer per entity:
itemsReducer,
itemReducer,
tagsReducer,
tagReducer.
This way each reducer will be responsible for its own data.
Here is a running example:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
const tagReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
newTagName
}
} = action;
const nextState = {
...state,
name: newTagName
}
return nextState;
}
default:
return state;
}
}
const tagsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
tagId
}
} = action;
const nextState = state.map(tag => {
if (tag.id !== tagId) return tag;
return tagReducer(tag, action);
});
return nextState;
}
default:
return state;
}
}
const itemReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const nextState = {
...state,
tags: tagsReducer(state.tags, action)
}
return nextState;
}
default:
return state;
}
}
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
itemId
}
} = action;
const nextState = state.map(item => {
if (item.id !== itemId) return item;
return itemReducer(item, action)
});
return nextState;
}
default:
return state;
}
};
console.log(itemsReducer(items, action));
This pattern is often called reducer composition, and you don't have to include all of them in your root reducer, just use them as external pure functions that will calculate the relevant portion of the state for your other reducers.
You forgot the key word return
//.....
const nextState = [
...state.slice(0, itemIndex),
nextItem,
...state.slice(itemIndex + 1)
];
// HERE RETURN 🎈
return newState;
}
default:
return state;

Angular 2 loop through a list with some delay

How do I loop through an array with some delay with Angular 2 and TypeScript?
I have an array,
students: Array<any> = [
{
name: "Alan"
},
{
name: "Jake"
},
{
name: "Harry"
},
{
name: "Susan"
},
{
name: "Sarah"
},
{
name: "Esther"
}
];
I want to loop through the list and display the names with a 2000ms delay.
<div *ngFor="let student of students">
{{student.name}}
</div>
doesn't work with a delay but is looping all at once.
Just use setTimeout. For example (* not tested):
students: Array<any> = [ ];
populateArrayWithDelay():void{
let people = [
{
name: "Alan"
},
{
name: "Jake"
},
{
name: "Harry"
},
{
name: "Susan"
},
{
name: "Sarah"
},
{
name: "Esther"
}
];
for(let i = 0; i < people.length; i++){
let student = people[i];
setTimeout(() => {
this.students.push(student);
}, 2000*(i+1));
}
}
Plunker example
export class App {
name:string;
students: Array<any> = [
{
name: "Alan"
},
{
name: "Jake"
},
{
name: "Harry"
},
{
name: "Susan"
},
{
name: "Sarah"
},
{
name: "Esther"
}
];
constructor() {
var timer = 0;
this.$students = Observable.from([[], ...this.students])
.mergeMap(x => Observable.timer(timer++ * 1000).map(y => x))
.scan((acc, curr) => {acc.push(curr); return acc;});
}
}

Resources