I'm building an Angular App with Ngrx and I'm encountering a problem. Here it is:
In the OnInit, i launch dispatch and selector for getting my datas from store and i want to edit these datas.
I get an error 'Cannot assign to read only property 'title' of object '[object Object]'' when i try to do that: x.title = ${x.title} (${count})
I understand why i can't reassign, state is immutable. Yes but, how can i edit my datas? I started by do that in the effect but it's display logic, i think i've to do that in a component logic.
There's my OnInit function:
ngOnInit() {
this.store.dispatch(new GetAllProducts());
this.store.select(selectResourcesList).pipe(
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
)
.pipe(tap((res => {
res.resources.map(x => {
let count = 0;
res.events.map(y => {
if (y.resourceId === x.id) {
count += y.extendedProps.quantity;
return count;
}
return count;
});
x.title = `${x.title} (${count})`;
});
})))
.subscribe((res) => {
this.resources = res.resources;
this.events = res.events;
this.cdr.detectChanges();
});
}
edit:
I had try to edit my data in the subscribe like this, but get the same error:
ngOnInit() {
this.store.dispatch(new GetAllProducts());
this.store.select(selectResourcesList).pipe(
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)))
.subscribe((res) => {
const tempResources = [...res.resources];
const tempEvents = [...res.events];
tempResources.map(x => {
let count = 0;
tempEvents.map(y => {
if (y.resourceId === x.id) {
count += y.extendedProps.quantity;
}
});
x.title = `${x.title} (${count})`;
});
this.resources = tempResources;
this.events = tempEvents;
this.cdr.detectChanges();
});
}
Thanks in advance for your help ;-)
I found a solution for bypass store immutability:
I had to create a copy of result of selector (with spread operator) and do the changes on it.
Like this:
ngOnInit() {
this.store.dispatch(new GetAllProducts());
this.store.select(selectResourcesList).pipe(
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)))
.subscribe((res) => {
let tempResources = [...res.resources];
let tempEvents = [...res.events];
tempResources = tempResources.map(x => {
let count = 0;
tempEvents = tempEvents.map(y => {
if (y.resourceId === x.id) {
count += y.extendedProps.quantity;
}
return y;
});
x = {
...x,
title: `${x.title} (${count})`
};
return x;
});
this.resources = tempResources;
this.events = tempEvents;
this.cdr.detectChanges();
});
}
Related
I'm new to Angular.
I have a demo array like this:
for (let index = 1; index <= 100; index++) {
this.listOfData.push([
`Name ${index}`,
`Math.floor(Math.random() * 100)`,
'Employee',
]);
}
And I try to create a filter table with search function:
onSearch = (value) => {
const output = this.listOfData.filter((arr) =>
arr.some((item) => {
item.toLowerCase().includes(value);
})
);
console.log('output: ', output);
this.listOfDisplayData = output;
};
This is my demo
So basically your data item is [[string,string,string],...] when you start filtering with filter you must return true for it to include and false to filter it out.
This should fix it
onSearch = (value) => {
const output = this.listOfData.filter((arr) =>
arr.some((item) => {
if (item.toLowerCase().includes(value)) {
return true;
} else {
return false;
}
})
);
console.log('output: ', output);
this.listOfDisplayData = output;
}
The problem I'm having is, that I have a useContext in which I provide all logged users. On the initial run of the app or when the users' log in the array gets populated with all the users that are currently on the server... Which works as expected. But I have also the functionality, that whenever the server "user-connected" event runs, the front-end should just push the user to the end of this array. And there lays the problem. From the backend, the right user is sent, but when I access the connectedUsers array, the array is empty... but it should be already populated.
UsersProvider.tsx
export const inState = {
connectedUsers: [],
addUser: (user: any) => {},
sortUsers: (user: any, socketID: string) => {},
setLoggedUsers: () => {},
};
export interface initState {
connectedUsers: any[];
addUser(user: any): void;
sortUsers(users: any, socketID: string): void;
setLoggedUsers: React.Dispatch < React.SetStateAction < any[] >> ;
}
const UsersContext = createContext < initState > (inState);
export const useUsers = () => {
return useContext(UsersContext);
};
const initUserProps = (user: any) => {
user.messages = [];
user.hasNewMessages = false;
};
export const UsersProvider = ({
children
}: Props) => {
const [connectedUsers, setLoggedUsers] = useState < any[] > ([]);
const addUser = (user: any) => {
console.log('add', connectedUsers);
// This is empty, but it should be already populated when next user connected.
};
const sortUsers = (users: any, socketUserID: string) => {
const usersCopy = users;
usersCopy.forEach((u: any) => {
for (let i = 0; i < usersCopy.length; i++) {
const existingUser = usersCopy[i];
if (existingUser.userID === u.userID) {
existingUser.connected = u.connected;
break;
}
}
u.self = u.userID === socketUserID;
initUserProps(u);
});
// put the current user first, and sort by username
let sorted = usersCopy.sort((a: any, b: any) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
setLoggedUsers([...sorted]);
};
return ( <
UsersContext.Provider value = {
{
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
}
} >
{
children
} <
/UsersContext.Provider>
);
};
And the part of ChatBoard.tsx, you can find addUser function initiated whenever user-connected happens. I really don't know why the would array be empty, if it is populated on the first run with users event.
const ChatBoard = (props: Props) => {
const socket = useSocket();
const {
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
} = useUsers();
useEffect(() => {
if (socket == null) return;
socket.on('users', (users) => {
console.log(users);
if (socket.userID) {
const socketID: string = socket ? .userID;
sortUsers(users, socketID);
}
});
socket.on('user-connected', (user: any) => {
console.log(user, 'this user connected!');
const connectingUser = user;
addUser(connectingUser);
});
socket.on('user-disconnected', (userID) => {
console.log('disconnected user');
const users = [...connectedUsers];
users.forEach((u) => {
if (u.userID === userID) {
u.connected = false;
setLoggedUsers([...users]);
}
});
});
return () => {
socket.off('users');
socket.off('user-connected');
};
}, [socket]);
CodeSandbox
So I have found the problem... so with React hooks sometimes a problem occurs called "Stale Closures", which means that React was picking up the old state (empty one, the one that was not yet populated and always returning that one.).
The solution to this problem, in my case is that when you use setState you use it with a callback. Like so, so you always get the latest state.
const addUser = (user: any) => {
setLoggedUsers((oldUsers) => {
const newUsers: any[] = [...oldUsers];
console.log(newUsers);
for (let i = 0; i < newUsers.length; i++) {
const existingUser = newUsers[i];
if (existingUser.userID === user.userID) {
existingUser.connected = true;
return newUsers;
}
}
initReactiveProperties(user);
newUsers.push(user);
return newUsers;
});
};
I have useContext which save all loggedUsers in an array. Each user has few properties, one of them is messages array, which stores objects with two properties. I am using this context through custom hook inside of component, like so: const { loggedUsers, setLoggedUsers } = useUsers();. The context looks like this:
export const UsersProvider = ({ children }: Props) => {
const [loggedUsers, setLoggedUsers] = useState<any[]>([]);
const socket = useSocket();
const addUser = (user: any) => {
console.log(typeof loggedUsers);
const users: any[] = loggedUsers;
users.push(user);
setLoggedUsers(users);
};
const sortUsers = (users: any) => {
users.forEach((user: any) => {
user.self = user.userID === socket?.id;
});
let sorted = users.sort((a: any, b: any) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
setLoggedUsers(sorted);
};
return (
<UsersContext.Provider
value={{ loggedUsers, setLoggedUsers, addUser, sortUsers }}
>
{children}
</UsersContext.Provider>
);
};
User in array looks like this:
{userID: 'Cm6vG0udcV6vl7MEAAAB', userName: 'test123', messages: [{message: 'dsada', fromSelf: true}], self: false, connected: true}
I am updating the context/state like so, but it doesn't force rerender, so my messages between users are not shown. What could be the problem? If I console.log loggedUsers the messages are there, but they do not show up on DOM. If you need more code, let me know.
socket.on('private message', listenerMessage);
const listenerMessage = async (msgInfo: any) => {
console.log(msgInfo);
console.log(msgInfo.from);
const usersList = loggedUsers;
for (let i = 0; i < usersList.length; i++) {
const user = usersList[i];
if (user.userID === msgInfo.from) {
user.messages.push({
message: msgInfo.message,
fromSelf: false,
});
if (user !== state.userID) {
user.hasNewMessages = true;
}
console.log(loggedUsers, 'onprivatemessage');
setLoggedUsers(usersList);
break;
}
}
};
const onMessage = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (state.userName) {
socket?.emit('private-message', {
message: mess,
to: state.userID,
});
const usersList = loggedUsers;
for (let i = 0; i < usersList.length; i++) {
const user = usersList[i];
if (user.userID === state.userID) {
user.messages.push({ message: mess, fromSelf: true });
console.log(loggedUsers, 'onmessage');
}
}
setLoggedUsers(usersList);
}
setMess('');
};
You shouldn't mutate useState's state directly.
Clone it before:
const usersList = [...loggedUsers]
const usersList = loggedUsers.slice()
Here :
setLoggedUsers(usersList);
You are passing the same state array to setLoggedUsers. Cloning the state will solve this.
What you can do is this :
setLoggedUsers(prev=>{
const prevUserList = prev.slice()
//your for loop or anything
return prevUserList;
});
Also here :
const usersList = loggedUsers;
for (let i = 0; i < usersList.length; i++) {
const user = usersList[i];
if (user.userID === state.userID) {
user.messages.push({ message: mess, fromSelf: true });
console.log(loggedUsers, 'onmessage');
}
}
You never update usersList , i don't know if that is wanted.
you using const while you updating the objects.
try using let and not const.
const objects should not be changed.
I have a list of objects, and I wanted to filter it based on a specific string property.
openTasks: Task[]; //Initial list
inProgressTasks: Task[] = []; //Filtered list
getTasks(): void {
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks().subscribe(tasks => this.openTasks = tasks);
// HERE I ACQUIRE LIST OF OPEN TASKS
} else {
this.taskService.getTaskByProjectId(this.projectId).subscribe(tasks => this.openTasks = tasks);
// HERE I ACQUIRE LIST OF OPEN TASKS
}
// FILTER
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
}
I received this error:
ERROR TypeError: Cannot read property 'filter' of undefined
Could you, please, help me with this error?
Your probably having problems here because of the async nature with which you fetch openTasks.
Try waiting for it to definitely finish before trying to filter it.
getTasks(): void {
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks().subscribe(tasks =>
{this.openTasks = tasks},
error => {},
() => { this.filterTasks()}
);
}
});
}
filterTasks() {
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
}
The this.openTasks is not set yet. it is only set after the this.openTasks = tasks in subscribe. this should work.
this.activatedRoute.paramMap.subscribe(params => {
this.projectId = +params.get('projectId');
if (this.projectId === 0) {
this.taskService.getTasks()
.subscribe(tasks => {
this.openTasks = tasks;
// your code that requirest openTasks
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
} else {
this.taskService.getTaskByProjectId(this.projectId)
.subscribe(tasks => {
this.openTasks = tasks
// your code that requirest openTasks
this.inProgressTasks = this.openTasks.filter(task => task.state === 'IN_PROGRESS');
});
}
});
Problem with got data correctly execute function many one times, these function is execute in ngOnInit one time with abstraction but i dont know ocurrs these problem in a server, i thing in snapshotChanges but i don't know.
thx for help
https://i.stack.imgur.com/EinQg.png
return <Observable<Products[]>> t.db.collection(PATHS_FIRESTORE.products).snapshotChanges()
.pipe(
map(actions => {
let arr = actions.map((res) => {
let doc: any = <any>res.payload.doc.data()
let obj: any = {}
if (!isNullOrUndefined(cart)) {
for (const prod in cart) {
if (cart.hasOwnProperty(prod)) {
const element = cart[prod];
if (doc.uid === prod) {
obj[doc.uid] = {
name_product: doc.name_product,
path_img: doc.path_img,
price: doc.price,
quantity: doc.quantity + element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
} else {
t.db.collection(PATHS_FIRESTORE.products).doc(prod).ref.get().then( res => {
const data = res.data()
return obj[res.id] = {
name_product: data.name_product,
path_img: data.path_img,
price: data.price,
quantity: element.total,
uid: doc.uid,
uid_local: doc.uid_local
}
})
}
}
console.log(obj)
}
return obj
}else {
obj = {
...doc
}
return obj
}
})
.filter((b: any) => {
return b.uid_local === uid_local
})
.filter((b: any) => {
return b.quantity > 0
})
.filter((b: any) => {
return !b.status
})
console.log(arr)
return arr
})
)