How to save the count on a counter app and present that count in a list showing previous counts made? (using react) - javascript

I have created a simple counter app that allows you to increment, decrement and reset the count. The next goal is to save the count, where you are able to show a history of previous calculations. Beginning with creating a function component for the 'Save Count' button.
I have gone about this by creating an empty useState array. Then creating an onClickFunction for saveCount, where the savedCount applies a spread operator to the count and then I map through the array every time the 'Save Count' button is pressed. This is based on a example of 'useState with Array' in the link below:
https://daveceddia.com/usestate-hook-examples/
You can find the code I have attempted below (also using this link: https://codesandbox.io/s/counter-app-project-save-nx7x6?file=/src/index.js:286-1297) :
function App() {
const [count, setCount] = useState(0);
const savedCount = useState([]);
const incrementCount = increment => {
setCount(count + increment);
};
const decrementCount = decrement => {
setCount(count - decrement);
};
const resetCount = () => {
setCount(0);
};
const saveCount = () => {
savedCount([
...count,
{
id: count.length
}
]);
resetCount();
};
return (
<div className="App">
<ButtonIncrement increment={1} onClickFunction={incrementCount} />
<ButtonIncrement increment={10} onClickFunction={incrementCount} />
<ButtonDecrement decrement={1} onClickFunction={decrementCount} />
<ButtonDecrement decrement={10} onClickFunction={decrementCount} />
<Reset onClickFunction={resetCount} />
<span>{count}</span>
<Save onClickFunction={saveCount} />
<ul>
{savedCount.map(count => (
<li key={count.id}>count</li>
))}
</ul>
</div>
);
}

function App() {
const [count, setCount] = useState(0);
const [savedCounts, setSavedCounts] = useState([]);
const incrementCount = increment => {
setCount(count + increment);
};
const decrementCount = decrement => {
setCount(count - decrement);
};
const resetCount = () => {
setCount(0);
};
const saveCount = () => {
setSavedCounts([...savedCounts, count]);
resetCount();
};
return (
<div className="App">
<ButtonIncrement increment={1} onClickFunction={incrementCount} />
<ButtonIncrement increment={10} onClickFunction={incrementCount} />
<ButtonDecrement decrement={1} onClickFunction={decrementCount} />
<ButtonDecrement decrement={10} onClickFunction={decrementCount} />
<Reset onClickFunction={resetCount} />
<span>{count}</span>
<Save onClickFunction={saveCount} />
<ul>
{savedCounts.map((savedCount, index) => (
<li key={index}>{savedCount}</li>
))}
</ul>
</div>
);
}

Fix savedCount as useState returns an array (tuple of [state,setState]).
Fix saveCount while using functional useState.
Render list items with unique key prop.
const [savedCount, setSavedCount] = useState([]);
const saveCount = () => {
setSavedCount(prev => [...prev, { id: count }]);
resetCount();
};
<ul>
{savedCount.map((count, index) => (
<li key={index}>count: {count.id}</li>
))}
</ul>

Related

How can I sum up a value from a loop of child components in react?

I have two react components one is Cart and the other is CartItem, Inside Cart I am looping using .map the CartItem. Inside CartItem I have a line total which is price * item.count. How can I sum up all the line totals regardless how many CartItems I have, and return the Total so that I can have the overall value of the cart.
I tried to add a handle total function but its only returning line total of one element not all of them.
Here is the CartItem:
const CartItem = ({item, handleTotal}) => {
var [itemCount, SetItemCount] = useState(1);
var [Linetotal, SetLineTotal] = useState(item.price);
const incrementcount = () => {
SetItemCount(++itemCount);
};
const decrementcount = () => {
SetItemCount(--itemCount);
};
useEffect(() => {
handleTotal(item.id,Linetotal)
},[])
return (
<div>
<Product>
<ProductDetail>
<Image src={item.image} />
<Details>
<ProductName>
<b>Product:</b> {item.name}
</ProductName>
<ProductId>
<b>ID:</b> {item.id}
</ProductId>
<ProductColor color={item.color} />
<ProductSize>
<b>Size:</b> {item.size}
</ProductSize>
</Details>
</ProductDetail>
<PriceDetail>
<ProductAmountContainer>
<Add onClick={() => {incrementcount(); SetLineTotal(item.price*itemCount)}} />
<ProductAmount>{itemCount}</ProductAmount>
<Remove onClick={() => {decrementcount(); SetLineTotal(item.price*itemCount)}} />
</ProductAmountContainer>
<ProductPrice>$ {item.price*itemCount}</ProductPrice>
</PriceDetail>
</Product>
</div>
);
};
export default CartItem;
and Here is the Cart:
const Cart = () => {
var [total, setTotal] = useState([]);
const handleTotal = (id,price) => {
setTotal(id,price);
};
return (
<Container>
<Navbar />
<Announcement />
<Wrapper>
<Title>YOUR BAG</Title>
<Top>
<TopButton>CONTINUE SHOPPING</TopButton>
<TopTexts>
<TopText>Shopping Bag(2)</TopText>
<TopText>Your Wishlist (0)</TopText>
</TopTexts>
<TopButton type="filled">CHECKOUT NOW</TopButton>
</Top>
<Bottom>
<Info>
{IntheCart.map((item) => (
<CartItem key={item.id} item={item} handleTotal={handleTotal} />
))}
<Hr />
</Info>
<Summary>
<SummaryTitle>ORDER SUMMARY</SummaryTitle>
<SummaryItem>
<SummaryItemText>Subtotal</SummaryItemText>
<SummaryItemPrice>$ 80</SummaryItemPrice>
</SummaryItem>
<SummaryItem>
<SummaryItemText>Estimated Shipping</SummaryItemText>
<SummaryItemPrice>$ 5.90</SummaryItemPrice>
</SummaryItem>
<SummaryItem>
<SummaryItemText>Shipping Discount</SummaryItemText>
<SummaryItemPrice>$ -5.90</SummaryItemPrice>
</SummaryItem>
<SummaryItem type="total">
<SummaryItemText>Total</SummaryItemText>
<SummaryItemPrice>$ 80</SummaryItemPrice>
</SummaryItem>
<Button>CHECKOUT NOW</Button>
</Summary>
</Bottom>
</Wrapper>
<Footer />
</Container>
);
};
export default Cart;
You can create updateTotal function in the Card component and once you increse or decrement item in the CardItem update the total value.
const CartItem = ({item, handleTotal, updateTotal}) => {
// code...
const incrementcount = () => {
SetItemCount(++itemCount);
updateTotal(item.price);
};
const decrementcount = () => {
SetItemCount(--itemCount);
updateTotal(-item.price);
};
// code...
updateTotal function.
var [totalPrice, setTotalPrice] = useState(0);
const updateTotal = (value) => {
setTotalPrice(prev => prev + value)
}
Add the updateTotal function to CardItem
<CartItem key={item.id} item={item} handleTotal={handleTotal} updateTotal={updateTotal} />
If you need sub sum to so sub total, you can do like this.
const Cart = () => {
...
var [subSum, setSubSum] = useState(null);
var [totalSum, setTotalSum] = useState(0);
const handleTotal = (id,price) => {
setSubSum({...subSum, id:price});
};
useEffect(()=>{
var sum = 0;
for (const [key, value] of Object.entries(subSum)) {
sum += value;
}
setTotalSum(sum);
},[total])
...
}

React: saving state of child component triggered by parent component

Is there a way to do this?
I want to save a ChildComponent's state into an array state from the GrandParentComponent whenever I click a button from the ParentComponent?
Here's an overview of my components:
const GrandParentComponent = () => {
const [array, setArray] = useState([]);
return (
<div>
<ParentComponent array={array} setArray={setArray} />
</div>
);
};
const ParentComponent = ({ array, setArray }) => {
const ref = useRef(null);
const handleClick = () => {
ref.current.setArray();
};
return (
<div>
<button onClick={handleClick}>save</button>
{array.map((item) => (
<ChildComponent array={array} setArray={setArray} ref={ref} />
))}
</div>
);
};
const ChildComponent = forwardRef(({ array, setArray }, ref) => {
const [childState, setChildState] = useState("")
useImperativeHandle(ref, () => {
return {
setArray: () => {
setArray((array) => [{ ...childState }, ...array]);
}
};
});
return <div>ChildComponent</div>;
});
The problem I'm encountering is it only saves the value of the last ChildComponent. What I want is to save all of the values from the ChildComponents.
What am I doing it wrong here? Or is there a better or correct way of doing what I'm trying to do?

usage of React.memo() inside components with prop functions

import React, { useState } from 'react'
const App = () => {
const [count, setCount] = useState<number>(0);
const [otherCount, setOtherCount] = useState<number>(0);
const increment = () => {
setCount((pre) => {
return pre + 1
})
}
const decrease = () => {
setOtherCount((pre) => {
return pre - 1
})
}
return (
<>
<DecrementComponent decrease={decrease} />
<br />
<br />
<IncrementComponent increment={increment} />
</>
)
}
const DecrementComponent = React.memo(({ decrease }: { decrease: () => void; }) => {
console.log("DecrementComponent");
return (
<div>
<button onClick={decrease}>Decrement</button>
</div>
)
})
const IncrementComponent = React.memo(({ increment }: { increment: () => void; }) => {
console.log("IncrementComponent");
return (
<div>
<button onClick={increment}>Increment</button>
</div>
)
})
export default App
**React.memo(), although I used React.memo(), when I clicked increment or decrement functions, two components were rendered.
But I think one component shoud be rendered in this senerio. Why were two component rendered ?
**
React.memo can only help if the props don't change. But the increment and decrement functions change on every render, so the props are always changing. You will need to memoize those functions so that they don't change.
const increment = useCallback(() => {
setCount((pre) => {
return pre + 1
});
}, []);
const decrement = useCallback(() => {
setCount((pre) => {
return pre - 1
});
}, []);

Avoid Inline Function Definition in the Render Function

I saw many article said that define inline function in render function in react can cause to performance issue.
Therefore they recommend to define the function outside of the render function and use it where i need (onClick etc).
I built a sample code that i have a list of button and each button will increase the state by the index in the list, But its throw error.
How i can pass parameter and not use inline function in onClick
const App = () => {
const [number, setNumber] = useState(1);
const increaseNumber = (num) => {
setNumber((prevState) => prevState + num);
};
return (
<div>
{[...Array(5)].map((item, index) => (
<button key={index} onClick={increaseNumber(index)}>
{`increase by ${index}`}
</button>
))}
<div>{number}</div>
</div>
);
};
export default App;
I'll preface my answer by saying you really should profile your application and identify specific performance issues before trying to optimize anything. In this case, you could avoid creating a new callback with each map iteration by using data attributes.
function App() {
const [number, setNumber] = useState(1);
const increaseNumber = (event) => {
const index = parseInt(event.target.dataset.index);
setNumber((prevState) => prevState + index);
};
return (
<div>
{[...Array(5)].map((item, index) => (
<button key={index} onClick={increaseNumber} data-index={index}>
{`increase by ${index}`}
</button>
))}
<div>{number}</div>
</div>
);
}
export default App;
Typically the only time you would care about creating a new callback per render is when the callback is used as a prop in a child component. Using something like useCallback can help to avoid unnecessary child renders in those cases.
Aproach 1: useMemo
When the arguments are fixed like it's your case, you may use useMemo:
import { useMemo, useState } from "react";
const indexes = [...Array(5)].map((_item, idx) => idx);
const App = () => {
const [number, setNumber] = useState(1);
const increaseNumber = useMemo(() => {
return indexes.map(index => () => setNumber(prevNumber => prevNumber + index));
}, [indexes]);
return (
<div>
{indexes.map(index => (
<button key={index} onClick={increaseNumber[index]}>
increase by {index}
</button>
))}
<div>{number}</div>
</div>
);
};
Approach 2: wraper component + useCallback
Create your own button component and pass the index:
const IncreaseButton = ({ setNumber, index }) => {
const increaseByIndex = useCallback(() => {
return setNumber(prevValue => prevValue + index);
}, [setNumber, index]);
return <button onClick={increaseByIndex}>increase by {index}</button>;
};
You can pass an item as a function that had been memoized to the onClick prop of react button elements.
const App = () => {
const [number, setNumber] = useState(1);
const increaseNumber = (num) => () => {
setNumber((prevState) => prevState + num);
};
const btns = useMemo(() => {
// here I am using lodash memoize function you may use your own
let inc = _.memoize(increaseNumber)
return Array(500).fill(0).map((_, index) => inc(index))
}, [])
return (
<div>
{btns.map((item, index) => (
<button key={index} onClick={item}>
{`increase by ${index}`}
</button>
))}
<div>{number}</div>
</div>
);
};

TODOLIST Update in REACT

So I was trying to update the value I got by the Addlist and I tried this but this isn;t working. Also when I click on the '+' button without writing anything, an empty list is created. How should I stop it. I've attached a code below.
import React from "react";
import "./App.css";
import { useState } from "react";
import TodoList from "./components/TodoList";
function App() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const updateList = (e) => {
setInput(e.target.value);
};
const AddList = () => {
console.log("value added")
setList((addValue) => {
return [...addValue, input];
});
setInput("");
};
const updateItems=(id)=>{
const newValue=[...list].map((newVal)=>{
if(input.id===id){
input.text='';
}
return newVal;
})
setList(newValue);
}
const deleteItems = (id) => {
console.log("deleted");
setList((addValue) => {
return addValue.filter((element, index) => {
return index !== id;
});
});
};
return (
<div className="todo-app">
<h1> Enter Anything</h1>
<input
type="text"
placeholder="Add anything"
value={input}
onChange={updateList}
/>
<button onClick={AddList}>+</button>
<ul>
{list.map((itemsvalue, id) => {
return (
<TodoList
itemsValue={itemsvalue}
key={id}
onSelect={deleteItems}
id={id}
onUpdate={updateItems}
/>
);
})}
</ul>
</div>
);
}
export default App;
Any kind of help would be appreciated. Also if I want to split this into multiple components is there a way to do.
When user clicks on the add button there is the check for empty String AddList method
for ex:- User updates second index value, second position value will get updated.
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [index, setIndex] = useState(null);
const updateList = (e) => {
setInput(e.target.value);
};
useEffect(() => {
setList(list);
console.log(list, '<>?');
}, [index]);
const AddList = () => {
if (input.trim() !== '') {
setList([...list, input]);
}
setInput('');
};
const updateValue = (index) => {
console.log(list[index]);
setIndex(index);
if (list[index].trim() !== '') {
setInput(list[index]);
}
};
const UpdateList = () => {
list[index] = input;
console.log(list, 'before <>?');
setIndex(null);
setInput('');
};
return (
<div>
<input type="text" placeholder="Add anything" value={input} onChange={updateList} />
<button disabled={!index && !list.length === 0} onClick={AddList}>
Add
</button>
<button disabled={input.trim() === ''} onClick={UpdateList}>
Update
</button>
{list.map((m, index) => (
<h1 style={{ border: '1px solid black' }} onClick={() => updateValue(index)}>
{m}
</h1>
))}
</div>
);

Categories