Push Unique Object in React.js - javascript

I'm building a terribly flawed e-commerce application using React just for fun and I'm trying to figure out how to set state on a certain object once it's pushed into an array.
I have a cart array where I push the items that I added from my initial items array which holds all my products.
The iffy thing is that I have stock on products. So let's say my chocolate product has 5 in stock and every time I push that the chocolate object, it piles on and adds the same item in the cart as so:
I want to be pushing the chocolates object to the cart array but I don't want to render a duplicate if it's already there. Instead I want to achieve something where the chocolate object is added but the quantity of it is changed accordingly every time it's added. It would look something like this:
How can I achieve something like this? Maybe a check to see if that object is already added to the cart array and if it is then instead of rendering a duplicate, just push the values and update a quantity of that item?
Been stuck for hours and would greatly appreciate some hints.
class App extends Component {
state = {
cart: [],
items: [
{ id: uuid(), name: 'chocolate', price: 10, remaining: 5 },
{ id: uuid(), name: 'strawberry', price: 50, remaining: 10 },
{ id: uuid(), name: 'banana', price: 43, remaining: 20 }
],
total: 0,
addToCartMessage: false,
removeFromCartMessage: false,
searchTerm: ''
}
addItem = item => {
const { cart, items, total } = this.state
cart.push({ id: item.id, name: item.name, price: item.price })
const remaining = item.remaining--
const totalPrice = cart.reduce((a, b) => a + b.price, 0)
this.setState({
total: totalPrice,
cart,
addToCartMessage: true,
...items, remaining
})
}
removeItem = cartItems => {
const { items, cart, total } = this.state
const removeItem = cart.filter(item => item.id !== cartItems.id)
const itemId = items.find(item => item.name === cartItems.name).remaining++
this.setState({
removeFromCartMessage: true,
total: total - cartItems.price,
cart: removeItem,
...items, remaining: itemId
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } =
this.state
if (addToCartMessage || removeFromCartMessage) {
setTimeout(() => {
this.setState({
addToCartMessage: false,
removeFromCartMessage: false
})
}, 1000)
}
const filteredItems = items.filter(item =>
item.name.includes(this.state.searchTerm))
return (
<div className="App">
{cart.length === 0 ? <h3>No items in cart</h3> : (
<div>
<h1>Cart:</h1>
{cart.map(items => (
<div key={items.id}>
<h1>{items.name} x 3</h1>
<p>${items.price}</p>
<button onClick={() => this.removeItem(items)}>Remove From Cart</button>
</div>
))}
</div>
)}
<hr />
<input
type="text"
placeholder="Search for an item..."
onChange={e => this.setState({ searchTerm: e.target.value })}
value={this.state.searchTerm}
/>
{filteredItems.map(item => (
<div key={item.id}>
<h1>{item.name}</h1>
<p>Price: ${item.price}</p>
{item.remaining === 0 ? <p>Sold Out</p> : (
<div>
<p>Remaining: {item.remaining}</p>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
{ total !== 0 ? <h1>Total ${total}</h1> : <h1>Total $0</h1> }
{ addToCartMessage && <h1>Item successfully added!</h1> }
{ removeFromCartMessage && <h1>Item successfully removed!</h1> }
</div>
)
}
}
export default App

Store your products in a regular object by id.
1: {
id: 1,
name: 'chocolate'
}
Store your cart as an array of IDs.
[1, 1, 1]
In your component, group IDs cart array by ID to get the count, and look up cart object by ID to get its data.
Computed data should be computed, not stored.
Here's some completely untested, unlinted, code showing the calculations done in the render function:
class App extends Component {
state = {
cart: [],
items: [
{ id: uuid(), name: 'chocolate', price: 10, available: 5 },
{ id: uuid(), name: 'strawberry', price: 50, available: 10 },
{ id: uuid(), name: 'banana', price: 43, available: 20 }
// Convert to an object of { id: { id, name, price } }
].reduce((memo, item) => ({
...memo,
[item.id]: item
}), {}),
}
addItem = id => {
const { cart, } = this.state
this.setState({
cart: [ ...cart, id ]
})
}
removeItem = removeId => {
const { cart, } = this.state
this.setState({
cart: cart.filter(({ id }) => id !== removeId)
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } = this.state
// Given an array of item IDs in our cart, group them into an object
// with the total count and price for each item, and overall count
const accumulatedItems = items.reduce((memo, item) => {
const { id, price } = item;
const { count, price, } = memo[id] || {};
return {
...memo,
cartTotal: memo.cartTotal + price,
[id]: {
count: (count || 0) + 1,
total: (price || 0) + price,
}
};
// Starting object to reduce
}, {
cartTotal: 0,
});
return (
<div className="App">
{cart.length === 0 ? <h3>No items in cart</h3> : (
<div>
<h1>Cart:</h1>
{Object.keys(accumulatedItems).sort().map(id => (
<div key={id}>
<h1>{items[id].name} x {accumulatedItems[id].total}</h1>
<p>${accumulatedItems[id].total}</p>
<button onClick={() => this.removeItem(id)}>Remove From Cart</button>
</div>
))}
</div>
)}
</div>
);
}
}
Juggling computed data, and mutating state like remaining, adds significant logic complexity to your app. You should only start worrying about trying to store/cache/memoize computed state once there are performance issues. Calculating totals in the render function is a fine first pass.
In React in the wild, there are solutions like reselect, which don't require redux technically. They're just caching functions that take in a given state, and produce a computed output, and only re-calculate the output if the input changes.

Maybe you can do it how you think and explain in your question. There are multiple ways of doing this and everyone do it how they like it or how easy they do and maintain it.
Instead of inserting the item itself maybe you can hold an object for each item in your array with item's unique id. That object also could hold quantity. Then you can generate card info via this unique id.
Example:
cart: [
{ id: uniqeId, quantiy: 1 },
{ id: uniqeId, quantiy: 6 },
]
After adding an item to card, you can go and just alter the related object's quantity. But for this you have to find the item in this array, then alter the quantity as you guess.
You can have item ids in your cart object (not array this time) as an array but this time you separate quantity and hold it as an object by item ids. So, after adding the item to cart's id array list, you also go and alter quantity of item's object. With this method you don't have to struggle finding anything but you need to alter two piece of information.
Like:
cart: {
ids: [ uniqueId, uniqueId ],
quantity: { uniqueId: 1, uniqueId: 6 }
}
Or you can do how you describe, just add the item but before doing this check if the item is already there. For example filtering by id. But, with this technique there might be some glitches. When you add items like this, for example with price, remaining etc, you also have to maintain your cart state with your item state. For instance what will happen when you want to change an item's price? Or what if there is another way (somehow) altering the remaining other then adding items into cart? But, if you play only with id's you can extract those information from your single state: items.
But I'm also a learner, maybe there are way better methods apart from those. I haven't written a cart yet, but if I did it I would use the second method maybe.
By the way, do not use push to alter your state. This mutates your state and it is not advisable. Use something like concat or spread operator to create a new state.
item = { foo: bar, fizz: buzz }
// now setting state
cart: [ ...this.state.cart, item ]
And try to use a callback function in setState (since it is an async operation) if your state change depends on the previous state.
this.setState( prevState => ( {
cart: [ ...prevState.cart, item ],
} ) )

Using #devserkan suggestion of restructuring the cart state and #Andy Ray's suggestion of restructuring the items state, I set up my state to look like this:
state = {
items: {
1: {
id: 1, name: 'chocolate', price: 10, available: 5
},
2: {
id: 2, name: 'strawberry', price: 10, available: 10
},
3: {
id: 3, name: 'banana', price: 10, available: 20
}
}
cart: {
ids: [],
quantity: { 1: 0, 2: 0, 3: 0 }
}
}
I then went and rendered out the items and added an onClick function (addItem) to handle some setState calls:
render() {
const { cart, items } = this.state
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>{item.price}</h2>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
))}
In my addItem function, I went ahead and set the state of cart so that I push the item id, and update the quantity on that id as well:
addItem = item => {
const { cart, items } = this.state
this.setState({
cart: {
...cart,
// Push item id to ids array inside cart state
ids: [...cart.ids, item.id],
quantity: {
...cart.quantity,
// Update quantity of the specific id pushed by 1
[item.id]: cart.quantity[item.id] + 1
}
}
})
}
Finally I had to render the cart section: I did so by checking to see if the cart.ids array wasn't empty and made another check to only render the item that has a quantity greater than 0. If we didn't make that check, every time we push an item, it will add all 3 at once and we only want that specific item to show.
{cart.ids.length !== 0 ? Object.keys(items).map(id => (
<div key={id}>
// Check to see if quantity for that item is > 0
{cart.quantity[id] > 0 && (
<h1>{items[id].name} x {cart.quantity[id]}</h1>
)}
</div>
)) : <h1>No Items In Your Cart</h1>}
Full Code (Without Price / Remaining)
export default class App extends Component {
state = {
cart: {
ids: [],
quantity: {
1: 0,
2: 0,
3: 0
}
},
items: {
1: {
id: 1, name: 'chocolate', price: 10, available: 5
},
2: {
id: 2, name: 'strawberry', price: 10, available: 10
},
3: {
id: 3, name: 'banana', price: 10, available: 20
}
}
}
addItem = item => {
const { cart, items } = this.state
this.setState({
cart: {
...cart,
ids: [...cart.ids, item.id],
quantity: {
...cart.quantity,
[item.id]: cart.quantity[item.id] + 1
}
}
})
}
removeItem = removeId => {
const { cart } = this.state
this.setState({
cart: cart.filter(({ id }) => id !== removeId)
})
}
render() {
const { cart, items, total, addToCartMessage, removeFromCartMessage } =
this.state
return (
<div className="App">
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>{item.price}</h2>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
))}
<hr style={{'marginTop': '200px'}} />
<h1>Cart</h1>
{cart.ids.length !== 0 ? Object.keys(items).map(id => (
<div key={id}>
{cart.quantity[id] > 0 && (
<h1>{items[id].name} x {cart.quantity[id]}</h1>
)}
</div>
)) : <h1>No Items In Your Cart</h1>}
</div>
)
}
}
Big thanks to #Andy Ray and #devserkan for the suggestions.

Related

React Hook Form move items between useFieldArray lists

I'm using React Hook Form to build a basic page builder application and it's been brilliant so far, I've been using the useFieldArray hook to create lists that contain items, however, I haven't found a way to move items between lists.
I know I can use the move() function to reorder items within the same list, however, since each list has its own nested useFieldArray I can't move the item from one list component to another list component.
If anyone knows of a way around this it would be much appreciated!
Here is a very simplified example of my current setup:
export const App = () => {
const methods = useForm({
defaultValues: {
lists: [
{
list_id: 1,
items: [
{
item_id: 1,
name: 'Apple'
},
{
item_id: 2,
name: 'Orange'
}
]
},
{
list_id: 2,
items: [
{
item_id: 3,
name: 'Banana'
},
{
item_id: 4,
name: 'Lemon'
}
]
}
]
}
});
return (
<FormProvider {...methods}>
<Page/>
</FormProvider>
)
}
export const Page = () => {
const { control } = useFormContext();
const { fields } = useFieldArray({
control,
name: 'lists'
})
return (
<ul>
{fields?.map((field, index) => (
<List listIdx={index} />
))}
</ul>
)
}
export const List = ({ listIdx }) => {
const { control, watch } = useFormContext();
const { fields, move } = useFieldArray({
control,
name: `lists[${sectionIdx}].items`
})
const handleMove = (prevIdx, nextIdx) => {
// this allows me to move within lists but not between them
move(prevIdx, nextIdx);
}
return (
<li>
<p>ID: {watch(lists[${listIdx}].list_id)}</p>
<ul>
{fields?.map((field, index) => (
<Item listIdx={index} itemIdx={index} handleMove={handleMove}/>
))}
</ul>
</li>
)
}
export const Item = ({ listIdx, itemIdx, handleMove }) => {
const { control, register } = useFormContext();
return (
<li>
<p>ID: {watch(lists[${listIdx}].items[${itemIdx}].item_id)}</p>
<label
Name:
<input { ...register('lists[${listIdx}].items[${itemIdx}]) }/>
/>
<button onClick={() => handleMove(itemIdx, itemIdx - 1)}>Up</button>
<button onClick={() => handleMove(itemIdx, itemIdx + 1)}>Down</button>
</div>
)
}
Thanks in advance!
If you'd not like to alter your default values (your data structure), I think the best way to handle this is using update method returning from useFieldArray. You have the data of both inputs that are going to be moved around, knowing their list index and item index, you could easily update their current positions with each other's data.

Removing an object from array based on properties value

I want to remove an object from my List array based on its properties value.
Right now I am using findIndex to see if there is an index ID matching my event.target.id.
This is an example of one of the objects in my list array:
{artist: "artist name",
genre: "RnB",
id: 1,
rating: 0,
title: "song name"}
This is my code:
console.log(e.target.id);
const list = this.state.playlist;
list.splice(
list.findIndex(function(i) {
return i.id === e.target.id;
}),
1
);
console.log(list);
}
how ever, instead of it removing the clicked item from the array, it removes the last item, always.
When I do this:
const foundIndex = list.findIndex((i) => i.id === e.target.id)
console.log(foundIndex)
I get -1 back.
What's the problem here?
Use filter for this. Use it to filter out the objects from state where the id of the button doesn't match the id of the current object you're iterating over. filter will create a new array you can then update your state with rather than mutating the existing state (which is bad) which is what is currently happening.
Assuming you're using React here's a working example.
const { Component } = React;
class Example extends Component {
constructor() {
super();
this.state = {
playlist: [
{id: 1, artist: 'Billy Joel'},
{id: 2, artist: 'Madonna'},
{id: 3, artist: 'Miley Cyrus'},
{id: 4, artist: 'Genesis'},
{id: 5, artist: 'Jethro Tull'}
]
};
}
// Get the id from the button (which will be
// a string so coerce it to a number),
// and if it matches the object id
// don't filter it into the new array
// And then update the state with the new filtered array
removeItem = (e) => {
const { playlist } = this.state;
const { id } = e.target.dataset;
const updated = playlist.filter(obj => {
return obj.id !== Number(id);
});
this.setState({ playlist: updated });
}
render() {
const { playlist } = this.state;
return (
<div>
{playlist.map(obj => {
return (
<div>{obj.artist}
<button
data-id={obj.id}
onClick={this.removeItem}
>Remove
</button>
</div>
);
})}
</div>
);
}
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

React how to handle add to Cart

So I have an e-commerce webpage but for some reason, after adding an item past the first time to a cart it starts to double the value of units in cart as well as double my Toasts. I am wondering what I am doing wrong here. My initial State is 0 for cartItem. Any help will be much appreciated.
here is what I am working with:
Example of cartItem Object:
[{
description: "...."
featured: false
id: "6u7pLcaGApuGtiNAf6zLMf"
image: ["//images.ctfassets.net/f1r553pes4gs/17rq7BQ76Q7ouq…010636019e9b3cbc3a10/il_794xN.2378509691_kaep.jpg"]
inCart: false
ingredients: (2) ["Distilled Water", "99.99% Fine Silver Rods"]
price: 20
productName: "Quintessence Colloidal Silver (4 fl oz)"
slug: "quintessence-colloidal-silver-4oz"
units: 9
}]
Shorted Version of Code:
export class StoreProvider extends Component {
//Initialized State ready for API Data
state = {
products: [],
featuredProducts: [],
sortedProducts: [],
price: 0,
maxPrice: 0,
minPrice: 0,
units: 0,
loading: true,
//FIXME CART
cartItem: []
}
handleAddToCart = (e, products) => {
this.setState(state => {
const cartItem = state.cartItem;
let productsAlreadyInCart = false;
cartItem.forEach(cp => {
if (cp.id === products.id) {
//Have tried ++
cp.units+= 1;
productsAlreadyInCart = true;
this.successfullCartToast()
}
});
if (!productsAlreadyInCart) {
cartItem.push({ ...products});
}
localStorage.setItem('cartItem', JSON.stringify(cartItem));
return { cartItem: cartItem };
});
}
}
//Button is in seperate component
<button
className="btn-primary rounded col-sm-6 col-lg-12 align-self-center ml-1 p-2"
onClick={(e) => handleAddToCart(e, product)}>
+ Cart
</button>
Issue
You are mutating existing state and toasting every cart item you check.
Solution
First search the cart array if item is already contained. If it is already contained then simply map the cart and update the appropriate index, otherwise, append to a shallowly copied cart item array.
Also, setState should be a pure function, so don't do side-effects like setting localStorage inside the setState functional update, instead use the setState callback, or preferably, the componentDidUpdate lifecycle function. Or you can just set localStorage with the same value you're updating state with.
handleAddToCart = (e, products) => {
const itemFoundIndex = this.state.cartItem.findIndex(
cp => cp.id === products.id
);
let cartItem;
if (itemFoundIndex !== -1) {
this.successfullCartToast();
// map cart item array and update item at found index
cartItem = this.state.cartItem.map((item, i) =>
i === itemFoundIndex ? { ...item, units: item.units + 1 } : item
);
} else {
// shallow copy into new array, append new item
cartItem = [...this.state.cartItem, products];
}
localStorage.setItem("cartItem", JSON.stringify(cartItem));
this.setState({ cartItem });
};
So, cartItem is an Object, looking something like:
[{'id': 123, 'units': 2}, {'id': 345, 'units': 1}, ...] ?
One thing to try would be to make a deep copy of the Object, so it doesn't changes while updating:
handleAddToCart = (e, products) => {
this.setState(state => {
//const cartItem = state.cartItem;
// make deep copy, so state doesn't change while manipulating:
const cartItem = JSON.parse(JSON.stringify( state.cartItem ));
let productsAlreadyInCart = false;
cartItem.forEach(cp => {
if (cp.id === products.id) {
//Have tried ++
cp.units += 1;
productsAlreadyInCart = true;
this.successfullCartToast()
}
});
if (!productsAlreadyInCart) {
cartItem.push({ ...products});
}
localStorage.setItem('cartItem', JSON.stringify(cartItem));
return { cartItem: cartItem };
});
}
}

Filter state in React without removing data

I'm trying to make a react component that can filter a list based on value chosen from a drop-down box. Since the setState removes all data from the array I can only filter once. How can I filter data and still keep the original state? I want to be able to do more then one search.
Array list:
state = {
tree: [
{
id: '1',
fileType: 'Document',
files: [
{
name: 'test1',
size: '64kb'
},
{
name: 'test2',
size: '94kb'
}
]
}, ..... and so on
I have 2 ways that I'm able to filter the component once with:
filterDoc = (selectedType) => {
//way #1
this.setState({ tree: this.state.tree.filter(item => item.fileType === selectedType) })
//way#2
const myItems = this.state.tree;
const newArray = myItems.filter(item => item.fileType === selectedType)
this.setState({
tree: newArray
})
}
Search component:
class SearchBar extends Component {
change = (e) => {
this.props.filterTree(e.target.value);
}
render() {
return (
<div className="col-sm-12" style={style}>
<input
className="col-sm-8"
type="text"
placeholder="Search..."
style={inputs}
/>
<select
className="col-sm-4"
style={inputs}
onChange={this.change}
>
<option value="All">All</option>
{this.props.docTypes.map((type) =>
<option
value={type.fileType}
key={type.fileType}>{type.fileType}
</option>)}
</select>
</div>
)
}
}
And some images just to get a visual on the problem.
Before filter:
After filter, everything that didn't match was removed from the state:
Do not replace original data
Instead, change what filter is used and do the filtering in the render() function.
In the example below, the original data (called data) is never changed. Only the filter used is changed.
const data = [
{
id: 1,
text: 'one',
},
{
id: 2,
text: 'two',
},
{
id: 3,
text: 'three',
},
]
class Example extends React.Component {
constructor() {
super()
this.state = {
filter: null,
}
}
render() {
const filter = this.state.filter
const dataToShow = filter
? data.filter(d => d.id === filter)
: data
return (
<div>
{dataToShow.map(d => <span key={d.id}> {d.text}, </span>)}
<button
onClick={() =>
this.setState({
filter: 2,
})
}
>
{' '}
Filter{' '}
</button>
</div>
)
}
}
ReactDOM.render(<Example />, 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>
<body>
<div id='root' />
</body>
Don't mutate local state to reflect the current state of the filter. That state should reflect the complete available list, which should only change when the list of options changes. Use your filtered array strictly for the view. Something like this should be all you need to change what's presented to the user.
change = (e) => {
return this.state.tree.filter(item => item.fileType === e.target.value)
}

Handle Input with Same State Value

I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</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>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<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>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}

Categories