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});
Related
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;
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>
I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.
I am trying to update my code: https://repl.it/#colegonzales1/HeftyJoyfulAbilities
I am trying to make my handleToggle function work properly. It is blank now for the sake of tinkering, but I have spent the last 2-3 hours trying to make it work to my own knowledge but I cannot figure out how to access a specific item in state. I know how to overwrite it all, but that is not what I want to do. Say I have:
this.state = {
todos: [
{
title: 'Wash car',
id: 1,
done: false
},
{
title: 'Go shopping',
id: 2,
done: false
}
],
inputValue: ''
}
How can I ONLY change the value of done on the 'Go shopping' todo, to true?
Use an array.map to toggle the done flag only on the element which matches the clicked id as follows. The other properties of the todo are copied with an object spread:
handleToggle (e) {
const id = parseInt(e.target.id,10)
this.setState((prevState) => ({
todos: prevState.todos.map(t => t.id === id ? {...t, done: !t.done} : t)
}))
}
You can find the index of the object with the given id with findIndex and create a new array with a copy of this object in it with its done flag toggled.
Example
class App extends React.Component {
state = {
todos: [
{
title: "Wash car",
id: 1,
done: false
},
{
title: "Go shopping",
id: 2,
done: false
}
],
inputValue: ""
};
handleToggle = id => {
this.setState(prevState => {
const todos = [...prevState.todos];
const todoIndex = todos.findIndex(todo => todo.id === id);
todos[todoIndex] = { ...todos[todoIndex], done: !todos[todoIndex].done };
return { todos };
});
};
render() {
return (
<div>
<div>{JSON.stringify(this.state)}</div>
<button onClick={() => this.handleToggle(2)}>
Toggle todo with id 2
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>
I'm using setState to update a particular object but for some reason, another object is being mutated as well even though I haven't added it in the setState function.
Basically I have an 'add to cart' function, if item already exists in cart state object, just increments it's quantity by 1. If not, update state of cart with a copy that contains said item.
this.state = {
items: {
0: { name: 'Red shirt', price: 10 },
1: { name: 'Blue shirt', price: 11 },
2: { name: 'Green shirt', price: 12 },
3: { name: 'Yellow shirt', price: 13 }
},
cart: {},
user: {}
}
addToCart = (key, item) => {
let newCart;
newCart = this.state.cart;
// item already exists in cart
if (newCart[key]) {
// increment qty of added item
newCart[key].qty++;
// does not exist, need to add to cart
} else {
newCart[key] = item;
newCart[key].qty = 1;
}
this.setState({ cart: newCart });
}
render() {
const { cart, items } = this.state;
return (
<div className="App">
<h1>Streat</h1>
<Checkout cart={cart} />
<Items
items={items}
addToCart={this.addToCart}
/>
</div>
);
}
}
// Items component which holds Item component
class Items extends Component {
constructor(props) {
super(props)
}
render() {
const { items, addToCart } = this.props;
return (
<div>
{
map(items, function renderItems(item, key) {
return <Item
item={item}
itemRef={key}
key={key}
addToCart={addToCart} />
})
}
</div>
)
}
}
// Item functional component which has the add to cart button
const Item = (props) => {
const { item, itemRef, addToCart } = props;
return (
<div>
<p>{item.name}</p>
<p>{item.price}</p>
<p>{item.qty || ""}</p>
<button onClick={() => addToCart(itemRef, item)}>Add</button>
</div>
)
}
When inspecting the state of my items object in react developer tools, I see that every time I click the button to add an item to the cart, it does correctly add the item to the cart / update the quantity of the item but it also updates the item in the items object in my state, adding a 'qty' key/ value pair which is not what I want.
I'm using setState on my cart object but my items object is being changed too for some reason.
Try to update your state by using spread syntax so you don't get into this kind of trouble. Redux also uses that a lot. By doing it you don't mutate your object and create a clean copy of them.
Your code is not copying the item on line (newCart[key] = item;). Instead, it is putting a reference from the same item and by changing the qty in the next line you consequently update it as well in the items key.
const Item = ({ name, price, onClick }) =>
<div onClick={onClick}>
{name} {price}
</div>
class App extends React.Component {
constructor() {
super()
this.state = {
items: {
0: { name: 'Red shirt', price: 10 },
1: { name: 'Blue shirt', price: 11 },
2: { name: 'Green shirt', price: 12 },
3: { name: 'Yellow shirt', price: 13 }
},
cart: {},
user: {}
}
}
addToCart(key, item) {
const hasItem = this.state.cart[key]
this.setState({
...this.state,
cart: {
...this.state.cart,
[key]: {
...(hasItem ? this.state.cart[key] : item),
qty: hasItem ? this.state.cart[key].qty + 1 : 1,
},
},
})
}
render() {
const { cart, items } = this.state
return (
<div>
<div>Click to add:</div>
{Object.keys(items).map(key =>
<Item
{...items[key]}
key={key}
onClick={this.addToCart.bind(this, key, items[key])}
/>
)}
<div style={{ marginTop: 20 }}>
{Object.keys(cart).map(key =>
<div key={key}>{cart[key].name}:{cart[key].qty}</div>
)}
</div>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>