I have an array that I am trying to shuffle on initial load, and onClick. My functions seem to be working but aren't visible unless there is a page rerender.
2 issues I'm trying to resolve:
I want to shuffle on initial load, but the shuffle doesn't occur unless there is a rerender.
I want to shuffle on button press, but the shuffle doesn't occur unless there is a rerender.
Here is my CodeSandbox
Thanks
import React, {
useEffect,
useState
} from "react";
const myArray = [{
name: "cat",
count: 0
},
{
name: "dog",
count: 0
},
{
name: "hamster",
count: 0
},
{
name: "lizard",
count: 0
}
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray, changes);
}
return (
<div>
{list.map((x, index) => (
<div key = {x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>
Shuffle
</button>
</div>
);
}
export default App;
HAI i have made some changes in App.js
import React, { useEffect, useState } from "react";
const myArray = [
{ name: "cat", count: 0 },
{ name: "dog", count: 0 },
{ name: "hamster", count: 0 },
{ name: "lizard", count: 0 }
];
function shuffle(arra1) {
var ctr = arra1.length,
temp,
index;
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = arra1[ctr];
arra1[ctr] = arra1[index];
arra1[index] = temp;
}
return arra1;
}
function App(props) {
const [list, setList] = useState(myArray);
useEffect(() => {
const mountArray = shuffle(myArray);
setList(mountArray);
}, []);
function handleShuffle() {
const changes = shuffle([...list]);
setList(changes);
console.log("Shuffle", myArray);
}
return (
<div>
{list.map((x, index) => (
<div key={x.name + x.index}>
{x.name} - {x.count}
<button onClick={() => setList([...list], (x.count = x.count + 1))}>
+
</button>
</div>
))}
<button onClick={handleShuffle}>Shuffle</button>
</div>
);
}
export default App;
Your setList function is used to modify the array and returns a new array, with shuffled values, so you need to apply that function non initial rendering.
useEffect(() => {
setList(shuffle(myArray))
}, []);
Html changes only if the state has changed, so make some state inside a component, and update it every time you want to update the html.
function App(props){
const [myArray, setMyArray] = useState([])
// rest of the code
}
Related
So ,I wanted to change this one product object in the UI with a button click, and reduce the quantity of the product. But the quantity amount did not change .What is the best move in situations like that.
let product={ a:1,b:2,c:3};
Lets say changing the value.
b:5;
please check this sample code
import React, { useState, useEffect } from 'react';
export default function App() {
const [products, setProducts] = useState([
{ name: 'Product 1', qty: 1, price: 10 },
{ name: 'Product 2', qty: 1, price: 20 },
]);
const [totalAmt, setTotalAmt] = useState(0);
useEffect(() => {
const total = products.reduce((s, i) => s += i.price * i.qty, 0);
setTotalAmt(total);
}, [products]);
const incrQty = (index) => {
setProducts((v) => {
const a = JSON.parse(JSON.stringify(v)); // FOR AVOIDING MUTATING DATA, BECAUSE IN STRICT MODE THIS SETSTATE CALLBACK WILL TRIGGER TWICE
a[index].qty += 1;
return [...a];
});
};
const decrQty = (index) => {
setProducts((v) => {
const a = JSON.parse(JSON.stringify(v));
a[index].qty -= 1;
return [...a];
});
};
return (
<div>
Total Amount: Rs.{totalAmt}
<ul>
{products.map((e, k) => (
<li key={k}>
{e.name}, qty={e.qty}, subtotal {e.qty * e.price},
<button onClick={e => incrQty(k)}>Incr Qty</button>
{
(e.qty > 1)?<button onClick={e => decrQty(k)}>Decr Qty</button>:null
}
</li>
))}
</ul>
</div>
);
}
So I have this component called counters where the state looks something like this
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
And I'm trying to increment the value every time a corresponding button is clicked. This is the function I wrote for it.
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
It doesn't work. And here are some additional observations.
When I console.log the local counters object (copied from state.counters) it returns an additional row at the end with id: -1 and value: NaN
The variable counter (thats being passed as a parameter) is from a child component. It's supposed to return 0 if the first button is clicked, 1 if the second button is clicked and so on. When I console.log it it seems to be returning the correct values.
by the looks of it, the problem seems to lie in the line
const index = counters.indexOf(counter);
As the value of index is always returned as -1.
You can use the index for updating the corresponding record in the array.
const idx = this.state.counters.findIndex((counter) => counter.id === id);
Complete implementation:-
import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 }
]
};
}
handleClick = (id) => {
const idx = this.state.counters.findIndex((counter) => counter.id === id);
const counters = [...this.state.counters];
counters[idx] = { ...counters[idx], value: counters[idx].value++ };
this.setState(counters);
};
render() {
return (
<div>
{this.state.counters.map((counter) => (
<div>
<button onClick={() => this.handleClick(counter.id)}>
Button {counter.id}
</button>
<span>Value {counter.value}</span>
<hr />
</div>
))}
</div>
);
}
}
Codesandbox - https://codesandbox.io/s/musing-black-cippbs?file=/src/App.js
try this for get the index:
const index = counters.findIndex(x => x.id === counter.id);
Adding my answer here just in case another confused soul stumbles upon it.
Although it seemed that the problem lied within line
const index = counters.indexOf(counter);
It actually was in the child component where the function was being invoked. Within the parameter I was passing counters.value whereas the handleincrement function within the parent component was expecting not the value, rather the complete object.
The code in its working condition is as below
Parent Component:
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 3 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
console.log(index);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
Child Component:
<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>
import React, { Component } from "react";
class Counter extends Component {
render() {
return (
<button
onClick={() => this.props.onIncrement(this.props.counter)}
className="btn btn-secondary btn-sm"
>
Increment
</button>
)
}
]
I have an orders component that contains some orders each order has products and the user can update the quantity and price the problem is that the update process is very slow because if I update a product quantity for example all products in the order get remounted again and I think this is the main issue. if I have 100 products the product page render 100 times or more (one time for each product ) here is my current implementation on code sandbox: https://codesandbox.io/s/holy-tdd-3nj7g?file=/src/OrderingPage/Order/index.js
here is the order component that have multiple order but for simplicity lets assume we only have one order
import { useState, useCallback } from "react";
import Order from "./Order/index";
const OrderingScreen = () => {
const initialOrderData = {
order: {
total: 0,
vat: 0,
deliveryCharge: 0,
date: 0,
orderStart: 0,
orderEnd: 0,
customerGender: "",
actualPaid: 0,
dateStr: "",
payType: "cash",
itemsCount: 0,
orderDetails: [
{ name: "prod1", sellPrice: 120, quantity: 3 },
{ name: "prod2", sellPrice: 12, quantity: 2 },
{ name: "prod3", sellPrice: 1123, quantity: 2 },
{ name: "prod4", sellPrice: 1543, quantity: 1 },
{ name: "prod5", sellPrice: 123, quantity: 8 }
]
}
//other properties
};
const [ordersData, setOrdersData] = useState([initialOrderData]);
const resetOrder = useCallback(() => {
let ordersDataCopy = [...ordersData];
ordersDataCopy[0] = initialOrderData;
setOrdersData(ordersDataCopy);
}, [ordersData]);
const updateOrderProducts = useCallback(
(products) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"]["orderDetails"] = [...products];
setOrdersData(ordersCopy);
},
[ordersData]
);
const updateOrder = useCallback(
(order) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"] = { ...order };
setOrdersData(ordersCopy);
},
[ordersData]
);
return (
<Order
order={ordersData[0].order}
products={ordersData[0].order.orderDetails}
updateOrderProducts={updateOrderProducts}
updateOrder={updateOrder}
resetOrder={resetOrder}
/>
);
};
export default OrderingScreen;
here is the single order component
import OrderItem from "./OrderItem";
import { useEffect, memo, useCallback } from "react";
const Order = ({ order, products, updateOrderProducts, updateOrder }) => {
const handleOrderChange = useCallback((propertyName, value) => {
let orderCopy = { ...order };
orderCopy[propertyName] = value;
updateOrder(orderCopy);
});
const deleteProduct = useCallback((index) => {
let productsCopy = [...products];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
updateOrderProducts(productsCopy);
}, []);
const handleOrderItemRemove = useCallback((index) => {
deleteProduct(index);
}, []);
const handleQuantityChange = useCallback((index, quantity) => {
let productsCopy = [...products];
productsCopy[index]["quantity"] = quantity;
updateOrderProducts(productsCopy);
}, []);
return (
<div className="d-flex px-2 flex-grow-1 mb-1">
{products.map((product, idx) => (
<OrderItem
product={product}
key={idx}
index={idx}
onRemove={handleOrderItemRemove}
onQuantityChange={handleQuantityChange}
updateProduct={handleOrderChange}
/>
))}
</div>
);
};
export default memo(Order);
and the last component which is the product component which I think is causing the performance issue (it render 1 + the number of products in the order if I update the quantity of one product )
import RemoveCircleIcon from "#mui/icons-material/RemoveCircle";
import AddCircleIcon from "#mui/icons-material/AddCircle";
import { memo, useMemo, useState, useEffect } from "react";
const OrderItem = ({ product, index, onQuantityChange }) => {
console.log("remount");
const [itemQuantity, setItemQuantity] = useState(product.quantity);
const incrementQuantity = () => {
onQuantityChange(index, itemQuantity + 1);
};
const decrementQuantity = () => {
itemQuantity > 1 && onQuantityChange(index, itemQuantity - 1);
};
useEffect(() => {
setItemQuantity(product.quantity);
}, [product.quantity]);
const productInfo = useMemo(() => (price, quantity, name) => {
let total = price * quantity;
total = +total.toFixed(2);
price = +price.toFixed(2);
return (
<div className={`col-9 col-xl-10 border rounded-start p-1 `}>
{name}
<div className="justify-content-around d-flex">
{"Price:" + price}
{" Quantity:" + quantity}
{" Total:" + total}
</div>
</div>
);
});
useEffect(() => {
setItemQuantity(product.quantity);
}, [product]);
const quantityColumn = (
<div>
<AddCircleIcon onClick={incrementQuantity} />
{itemQuantity}
<RemoveCircleIcon onClick={decrementQuantity} />
</div>
);
return (
<div style={{ marginBottom: "25px" }}>
{productInfo(product.sellPrice, product.quantity, product.name)}
{quantityColumn}
</div>
);
};
export default memo(OrderItem);
what I want to achieve is a snappy component update (maybe by making the product component mount only for the changed product)
you may see it fast on the sandbox but this version just explains the problem only... the real version is much complicated
You can improve performance by changing your React.memo components.
Instead of memo(OrderItem) pass as second argument function that will compare previous and current state:
function areEqualOrderItem(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return prevProps.quantity === nextProps.quantity;
}
export default memo(OrderItem, areEqualOrderItem);
Also I suggest do not use array index as component key try product name instead of this.
useCallback do nothing in your code. Instead you can use this one:
const handleOrderItemRemove = useCallback((index) => {
updateOrderProducts(prod => {
let productsCopy = [...prod];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
return productsCopy;
});
}, [updateOrderProducts]);
const updateOrderProducts = useCallback(
(products) => {
setOrdersData(ords => {
let ordersCopy = [...ords];
ords[0]["order"]["orderDetails"] = [...products];
return ordersCopy;
});
},
[setOrdersData]
);
When you fix all your callbacks you can boost performance. At this time, your code cause rerender of all items almost every small change.
I want my function to roll 5 dices and store them in my dices state.
In the recent version, i am only storing one object for the next click, but i made the for loop to generate 5 dices at once, how can i store the previous dce object(s) for the next iteration in the for loop?
import './App.css';
import Subgroup from './Subgroup'
import React, {useState} from 'react'
function App() {
const [numberRolls, setNumberRolls] = useState(3);
const [dices, setDices] = useState([]);
return (
<div className="App">
<Subgroup
numberRolls = {numberRolls}
setNumberRolls = {setNumberRolls}
dices = {dices}
setDices = {setDices}
/>
</div>
);
}
export default App;
import React from 'react'
const Subgroup = ({numberRolls, setNumberRolls, dices, setDices}) => {
const rollTheDice = () => {
if(numberRolls > 0) {
setNumberRolls(numberRolls - 1);
for(let i = 0; i < 5; i++) {
setDices([...dices, // i think sth should be different here????
{id: Math.random()*100,
number: Math.ceil(Math.random()*6)-1,
selected: false}
])
}
}
console.log(dices)
}
return (
<div>
<button onClick={ rollTheDice }>blablabala </button>
</div>
)
}
export default Subgroup;
Try saving the result in an array, then set the state, something like:
const result = [];
for(let i = 0; i < 5; i++) {
result.push({
id: Math.random() * 100,
number: Math.ceil(Math.random() * 6) -1,
selected: false
});
}
setDices(prev => [...prev, ...result]);
I'm learning React hooks so in order to do that I'm trying to convert a class component to a functional component but I still get some errors.
Here is the original working component written as a class:
import React, { Component } from 'react';
import NavBar from './components/navbar';
import Counters from './components/counters';
class App extends Component {
state = {
counters: [
{ id: 0, value: 5 },
{ id: 1, value: 1 },
{ id: 2, value: 2 },
],
};
handleDelete = (counterId) => {
const counters = this.state.counters.filter((c) => c.id !== counterId);
this.setState({ counters });
};
handleReset = () => {
const counters = this.state.counters.map((c) => {
c.value = 0;
return c;
});
this.setState({ counters });
};
handleIncrement = (counter) => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
counters[index] = { ...counter };
counters[index].value++;
this.setState({ counters });
};
render() {
return (
<React.Fragment>
<NavBar
totalCounters={this.state.counters.filter((c) => c.value > 0).length}
/>
<main className='container'>
<Counters
counters={this.state.counters}
onReset={this.handleReset}
onDelete={this.handleDelete}
onIncrement={this.handleIncrement}
/>
</main>
</React.Fragment>
);
}
}
export default App;
And this is the converted version which uses hooks.
import React, { useState } from 'react';
import NavBar from './components/navbar';
import Counters from './components/counters';
const App = () => {
const [counters, setCounters] = useState([
{ id: 0, value: 5 },
{ id: 1, value: 1 },
{ id: 2, value: 2 },
]);
const handleDelete = (counterId) => {
const counterss = counters.filter((c) => c.id !== counterId);
setCounters({ counterss });
};
const handleReset = () => {
const counterss = counters.map((c) => {
c.value = 0;
return c;
});
setCounters({ counterss });
};
const handleIncrement = (counter) => {
const counterss = [...counters];
const index = counterss.indexOf(counter);
counterss[index] = { ...counter };
counterss[index].value++;
setCounters({ counterss });
};
return (
<React.Fragment>
<NavBar totalCounters={counters.filter((c) => c.value > 0).length} />
<main className='container'>
<Counters
counters={counters}
onReset={handleReset}
onDelete={handleDelete}
onIncrement={handleIncrement}
/>
</main>
</React.Fragment>
);
};
export default App;
Most of it works fine but it keeps throwing an error saying that filter is not a function. Here it is the message:
TypeError: counters.filter is not a function
The main culprit appears to be the way you are updating your state, which is like this:
setCounters({ counterss });
This will actually set your counters state to an object with the property counterss, so your state will contain the following:
{ counterss: [/* rest of the array */] }
The exact error being thrown is referring to the fact that you are attempting to call .filter on an object instead of an array. To fix this issue you should simply update your state like this:
setCounters(counterss);
setCounters({ counterss })
should be
setCounters(counterss)
It throws an error because you set the new state as an object setCounters({ counterss });. But you want an array: setCounters(counterss);. This way it won't throw an error the second time setCounters is called.