React state not updating on second index - reactjs

I have nested objects as described below and updating states.
`
interface BookState {
name: string
authors: AuthorState[]
}
interface AuthorState {
name: string
}
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial)
// Add new empty author; which will later be filled from textfields
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { } as AuthorState
let authors = [...bookValues[bookIndex].authors, newAuthor]
let newBookState = update(bookValues, { [bookIndex]: { authors: { $set: authors } } })
setBookValues(newBookState) // ** edited
}
// somewhere i populate bookValues as:
bookValues = [
{name: "Book-1", authors: [{name: "Author-1"}] },
{name: "Book-2", authors: [{name: "Author-1"}, {name: "Author-2"}]}
]
`
When I add an author, suppose in "Book-1" index 0, I call the onClickAddAuthor(0), the state updates and UI updates. But when I add an author, suppose in "Book-2" index 1, i call the onClickAddAuthor(1), the state value can be seen updating when printing to console but the UI does not update. I am using https://github.com/kolodny/immutability-helper to update the state.
I expect to add a new empty author on index-1 as well, which should update the state and UI. I tried making deep copies of the book Values and updating the state with that, but it is not working. If it is working in index 0, it should work on other indexes (1, 2, 3 .. ) as well. I am not able to understand.

I tested the posted code with 4 items in bookValues, it seems that the onClickAddAuthor is working as expected. Perhaps the output logic could be checked to see if it updates correctly.
Simple test demo on: stackblitz
import { useState } from 'react';
import './App.css';
import update from 'immutability-helper';
interface AuthorState {
name: string;
}
interface BookState {
name: string;
authors: AuthorState[];
}
const bookStateInitial = [
{ name: 'Book-1', authors: [{ name: 'Author-1' }] },
{ name: 'Book-2', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
{ name: 'Book-3', authors: [{ name: 'Author-1' }] },
{ name: 'Book-4', authors: [{ name: 'Author-1' }, { name: 'Author-2' }] },
];
function App() {
const [bookValues, setBookValues] = useState<BookState[]>(bookStateInitial);
const onClickAddAuthor = (bookIndex: number) => {
let newAuthor = { name: 'Test Author' } as AuthorState;
let authors = [...bookValues[bookIndex].authors, newAuthor];
let newBookState = update(bookValues, {
[bookIndex]: { authors: { $set: authors } },
});
setBookValues(newBookState);
};
return (
<main className="App">
<section>
{[0, 1, 2, 3].map((item) => (
<button key={item} onClick={() => onClickAddAuthor(item)}>
{`Test: add author for Book-${item + 1}`}
</button>
))}
</section>
<ul>
{bookValues.map((book) => (
<li key={book.name}>
{`name: ${book.name}, authors: ${book.authors
.map((author) => author.name)
.join(', ')}`}
</li>
))}
</ul>
</main>
);
}
export default App;

Related

Using find and map together React

We have chat app build on React
const Chat = ({ thread }) => {
return (
<div className='thread'>
{thread.map((message, index) =>
<Message message={message} key={index} repliedMessage={message}/>
)}
</div>
);
};
export default class App extends React.Component {
state = {
thread: [
{
id: 1,
user: 'John',
text: 'Hellow'
},
{
id: 2,
user: 'Jim',
replyTo: 1,
text: 'Hi'
},
{
id: 3,
user: 'Jack',
replyTo: 2,
text: 'Cheers :)'
}
]
};
App must show what message have been replied.
The question is - how I can use FIND method with MAP in Chat component?
I don't think you need to use the find method here:
thread.map((message, index) => {
if(message.replyTo) {
return ...
} else {
return ...
}
}
)

React/Firebase. How can i filter some products by categories using firebase?

How can i filter some products by categories using firebase? This is a fragment of my code
Not sure if you have a correct db.json file, i had to flatMap the result but here is a working code. I used require to load you json file and left const [products, setProducts] = useState([]); just in case. Also i switched categories to useMemo so this variable will not update on each re-render.
import React, { useState, useEffect, useMemo } from "react";
import "./styles.scss";
import { Link } from "react-router-dom";
const dbProducs = require("./db.json");
const CategoriesPage = () => {
// const {product} = useContext(Context)
const [products, setProducts] = useState([]);
const categories = useMemo(() => {
return [
{ id: 1, title: "Tablets" },
{ id: 2, title: "Computers" },
{ id: 3, title: "Consoles" },
{ id: 4, title: "Photo and video" },
{ id: 5, title: "Technics" },
{ id: 6, title: "Game Content" },
{ id: 7, title: "Notebooks" },
{ id: 8, title: "Smartphones" },
{ id: 9, title: "Headphones" },
{ id: 10, title: "Steam" }
// {id: 11,imageSrc:steamcards, title: 'Стиральные машины'},
// {id: 12,imageSrc: coffeemaschine, title: 'One stars'},
// {id: 13,imageSrc:headphones, title: 'Холодильники'},
];
}, []);
useEffect(() => {
const flatMapped = dbProducs.flatMap((x) => x.products);
setProducts(flatMapped);
}, []);
return (
<section className="popular__categories">
<h3 className="events__title">
<span>Categories</span>
</h3>
<div className="categories__wrapper">
{categories.map((category) => (
<Link
to={`${category.id}`}
className="categories__content"
key={category.id}
>
<h2 className="categories__title">{category.title}</h2>
<img
className="categories__img"
alt={category.title}
src={category.imageSrc}
/>
<ul>
{products
.filter((p) => p.category === category.title)
.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</Link>
))}
</div>
</section>
);
};
export default CategoriesPage;
Technically it would be better to clone and extend your categories objects with additional array property with useMemo, or you can add additional Map object with key = Category(title) and value = products (filtered) but it is up to you.
Full example with Context, Routes, Navigation:

Migration to Mobx 6: functional components aren't working with decorated observables

I faced with problem while migrating from Mobx 4 to Mobx 6.
I have a functional component but after updating Mobx it stopped working. Looks like store doesn't works. Component react on changes inside observable variable by reaction feature but changes aren't re-rendering. I made everything that was provided in migration guide but component's store doesn't working.
At some reason if I change functional component to class component everything starts working. But I really can't understand the reason why such happens and can't find any explanation of such behaviour.
Case looks like example bellow. Experimental decorators are enabled and any other stuff that was provided in Migration guide as well. So what is the reason of such behaviour and how can I implement correct logic in functional component?
interface User {
name: string;
age: number;
info: {
phone: string;
email: string;
};
}
const usersData: User[] = [
{
name: "Steve",
age: 29,
info: {
phone: "+79011054333",
email: "steve1991#gmail.com",
},
},
{
name: "George",
age: 34,
info: {
phone: "+79283030322",
email: "george_the_best_777#gmail.com",
},
},
{
name: "Roger",
age: 17,
info: {
phone: "+79034451202",
email: "rodge_pirat_yohoho#gmail.com",
},
},
{
name: "Maria",
age: 22,
info: {
phone: "+79020114849",
email: "bunnyrabbit013#gmail.com",
},
},
];
const getUsers = () => {
return new Promise<User[]>((resolve) => {
setTimeout(() => {
resolve(usersData);
}, 2000);
});
};
class Store {
#observable users: User[] = [];
constructor() {
makeObservable(this);
}
async init() {
const users = await getUsers();
this.setUsers(users);
}
#action setUsers(users: User[]) {
this.users = users;
}
#action increaseUserAge(userIndex: number) {
const users = this.users.map((u, k) => {
if (k === userIndex) {
u.age += 1;
}
return u;
});
this.setUsers(users);
}
#computed get usersCount(): number {
return this.users.length;
}
}
const store = new Store();
const UserList = observer(() => {
React.useEffect(() => {
store.init();
}, []);
const addOneUser = () => {
const user = {
name: "Jesica",
age: 18,
info: {
phone: "+79886492224",
email: "jes3331#gmail.com",
},
};
store.setUsers([...store.users, user]);
};
return (
<div className="App">
<h4>Users: {store.usersCount}</h4>
{store.users.length ? (
<>
<ul>
{store.users.map((user, key) => (
<li key={key}>
Name: {user.name}, Age: {user.age}, Info:
<div>
Phone: {user.info.phone}, Email: {user.info.email}
</div>
<button onClick={() => store.increaseUserAge(key)}>
Increase Age
</button>
</li>
))}
</ul>
<button onClick={addOneUser} disabled={store.usersCount >= 5}>
Add one user
</button>
</>
) : (
<p>Fetching users...</p>
)}
</div>
);
});
function App() {
return <UserList />;
}
export default App;
I've made Codesandbox example with your code (although removed types), it works fine.
Check tsconfig.json there, maybe you forgot to enable some of the options?
Or check what versions of mobx and mobx-react are you using?
And just a small nitpick on how you use your increaseUserAge action, it can be as simple as that:
#action increaseUserAge(user) {
user.age += 1;
}
And in the jsx you just pass the whole user there:
<button onClick={() => store.increaseUserAge(user)}>
Increase Age
</button>

How to insert a heading before the group of mapped items created on the same date inside the map function?

I'm trying to map an array of objects like this:
import React from 'react';
import moment from 'moment';
const designItems = () => {
const itemsList = [
{
id: 30,
name: "Item 30",
created_at: "2020-12-09T09:23:58.936Z"
}, // etc.
];
const items = itemslist.map((item, i) => {
const monthName = moment(item.created_at, 'YYYY-MM-DDTHH:mm:ss.SSS[Z]').format('MMMM');
const yearName = moment(item.created_at, 'YYYY-MM-DDTHH:mm:ss.SSS[Z]').format('YYYY');
return (
<p key={item.id}>{item.name}</p>
);
});
return (
<div>
{items}
</div>
);
};
export default designItems;
The array itemsList is sorted by descending date the items were created at (created_at).
Is there a way to insert a heading containing monthName and yearName before each group of paragraphs that were created at same year and month so the output looks like this?
<div>
<h3>December 2020</h3>
<p>Item 30</p>
<p>Item 29</p>
<h3>November 2020</h3>
<p>Item 28</p>
<p>Item 27</p>
...
<div>
how are you?
You can create a structure that groups the items by month/year. You can write a function to do that first and then you iterate over this structure. For example:
function groupItemsByYearAndMonth(items) {
const grouped = {}
items.forEach(item => {
const yearName = moment(item.created_at, 'YYYY-MM-DDTHH:mm:ss.SSS[Z]').format('YYYY');
const monthName = moment(item.created_at, 'YYYY-MM-DDTHH:mm:ss.SSS[Z]').format('MMMM');
const key = `${monthName} ${yearName}`;
// if the key does not exists, we create it
if (!grouped[key]) {
grouped[key] = [];
}
grouped[key].push(item);
})
return grouped;
}
The function above will return an object in which the keys are the year+month and the value of each key will be an array of items, for example:
{
'December 2020': [
{name: 'Item 30'},
{name: 'Item 29'},
]
'November 2020': [
{name: 'Item 28'},
{name: 'Item 27'},
]
}
Now you can iterate over the object using Object.entries(groupedItems).map([yearAndMonth, items]). Se the example bellow:
const App = () => {
const list = [
{
id: 1,
name: "Item 1",
created_at: "2020-12-09T09:23:58.936Z"
},
{
id: 2,
name: "Item 2",
created_at: "2020-12-08T09:23:58.936Z"
},
{
id: 3,
name: "Item 3",
created_at: "2020-11-09T09:23:58.936Z"
},
{
id: 4,
name: "Item 4",
created_at: "2020-11-08T09:23:58.936Z"
},
];
const groupedItems = groupItemsByYearAndMonth(list);
const renderItems = items => {
return items.map(item => (
<p key={item.name}>{item.name}</p>
))
}
return (
<div>
{Object.entries(groupedItems).map(([yearMonth, items]) => (
<>
<h3>{yearMonth}</h3>
{renderItems(items)}
</>
))}
</div>
);
}
I have create this Code Pen so you can check it out. :)

How to add values to Array using map in React

I have the following Array
arrayOfItems: [{
0:
description: "item1"
id: 11
name: "item1Name"
},
1:
description: "item2"
id: 12
name: "item2Name"
},
2:
description: "item3"
id: 13
name: "item3Name"
},
3:
description: "item4"
id: 14
name: "item4Name"
}]
I want to add a new pair
{
description: "item5"
id: 15
name: "item5Name"
}
I am still very new to React and have been working on this problem. I do understand how Map works but not sure how I can add new pair in React
This component is a dropdown list so there is no input or button click related to it.
{dataArray.arrayOfItems!.map((item: any) => {
return (
<ComponentName key={item.id} value={item.description}>
{item.description}
</ComponentName>
);
})}
if you want to add item to array on page load use componentDidMount() method:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
items:[
{id:1,name:'aaa', description:'this is description aaa'},
{id:2,name:'bbb', description:'this is description bbb'},
]
}
}
componentDidMount(){
let items=this.state.items;
let newItem={id:5,name:'ccc',description:'this is description ccc'};
let updatedItems=items.push(newItem);
// or you can use ... spread operator
// let updatedItems=[...items,newItem];
this.setState({items:updatedItems});
}
}
You can store your array into state, and then modify the state.
Here's an example
function MyComponent() {
const [items, setItems] = React.useState([{ id: 0, description: 'Old Item' }])
const loadMoreItems = () => {
setItems([...items, { id: 1, description: 'New Item' }])
}
return (
<>
{items.map((item) => (
<div key={item.id} value={item.description}>
<p>{item.description}</p>
</div>
))}
<button onClick={loadMoreItems}>Load more items</button>
</>
)
}
Add on change event to your dropdown.
onChange = (event) => {
console.log(event.target.value)
// add your value to array here
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
<select onChange={this.onChange}>
</select>
EDIT
Adding values on page load. Don't use push to add items to array in state.
componentDidMount = () => {
this.setState((prevState) => {
arrayOfItems: [...prevState.arrayOfItems, yourItem],
})
}
let fileInfos=this.state.fileInfos;
fileInfos.push({
"name": file.name,
"content": e.target.result
});
this.setState({fileInfos});

Resources