Unusual behavior when value of variable change (easy) - javascript

Happy 2k22! I am building a countdown timer. I have two files. The first file takes the countDown time from input and the second from select dropdown .
I have implemented an increment button in the first file. It increases the countDown time by inc seconds i.e. time = time + inc.
So what's peculiar?
Thing is that when inc is replaced with any constant value, it works properly.
<button onClick={() => setSecondsRemaining(secondsRemaining + 3)}>
Increment {inc} sec
</button>
But when I used the input to enter the value and supply it through the variable inc, then it does not work. It increases randomly.
<button onClick={() => setSecondsRemaining(secondsRemaining + inc)}>
Increment {inc} sec
</button>
You can visit InputCountDown.js here
This is the full code:
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";
const STATUS = {
STARTED: "Started",
STOPPED: "Stopped"
};
export default function InputCountDown() {
const [time, setTime] = useState(0);
const [inc, setInc] = useState(0);
const handleOnChange = (e) => {
//console.log(e.target.value);
setTime(e.target.value);
};
const handleOnChangeIncrement = (e) => {
console.log(e.target.value);
setInc(e.target.value);
};
const [secondsRemaining, setSecondsRemaining] = useState(time * 60);
//console.log(time);
const [status, setStatus] = useState(STATUS.STOPPED);
const secondsToDisplay = secondsRemaining % 60;
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60;
const minutesToDisplay = minutesRemaining % 60;
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60;
const handleStart = () => {
setStatus(STATUS.STARTED);
setSecondsRemaining(time * 60);
};
const handleStop = () => {
setStatus(STATUS.STOPPED);
};
const handleReset = () => {
setStatus(STATUS.STOPPED);
setSecondsRemaining(time * 60);
};
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1);
} else {
setStatus(STATUS.STOPPED);
}
},
status === STATUS.STARTED ? 1000 : null
// passing null stops the interval
);
return (
<>
<div className="App">
<h1>Countdown Using Input</h1>
<div style={{ padding: "12px" }}>
<label htmlFor="time"> Enter time in minutes </label>
<input
type="text"
id="time"
name="time"
value={time}
onChange={(e) => handleOnChange(e)}
/>
</div>
<div style={{ padding: "12px" }}>
<label htmlFor="inc"> Enter increment </label>
<input
type="text"
id="inc"
name="inc"
value={inc}
onChange={(e) => handleOnChangeIncrement(e)}
/>
</div>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div style={{ padding: 20, fontSize: "40px" }}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
<div>
<button onClick={() => setSecondsRemaining(secondsRemaining + inc)}>
Increment {inc} sec
</button>
</div>
</div>
<div>Status: {status}</div>
</div>
</>
);
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, "0");
You can visit InputCountDown.js here
Please help me.

It's a very basic rule you missed mate. Whenever you get anything from TextInput, its always a string, even if you entered an integer. Try this:
<button onClick={() => setSecondsRemaining(secondsRemaining + parseInt(inc))}>
Increment {inc} sec
</button>

When consuming values from input elements recall that these are always string type values. Best to maintain the number type state invariant, so do the conversion from string to number when updating state.
const handleOnChange = (e) => {
setTime(Number(e.target.value));
};
const handleOnChangeIncrement = (e) => {
setInc(Number(e.target.value));
};
To help ensure users are entering number-like data into the inputs, specify each input to be type="number".
<div style={{ padding: "12px" }}>
<label htmlFor="time"> Enter time in minutes </label>
<input
type="number" // <-- number type
id="time"
name="time"
value={time}
onChange={handleOnChange}
/>
</div>
<div style={{ padding: "12px" }}>
<label htmlFor="inc"> Enter increment </label>
<input
type="number" // <-- number type
id="inc"
name="inc"
value={inc}
onChange={handleOnChangeIncrement}
/>
</div>
Recall also that when enqueueing state updates that if the next state value depends on the previous state value, i.e. when incrementing counts, etc..., that you should use a functional state update.
<button onClick={() => setSecondsRemaining(time => time + inc)}>
Increment {inc} sec
</button>

Related

How to show the final state from UseState in my CookieClicker?

i am a beginner with react and im working on a little clickergame.
My problem is, that i want to use useState to automaticly increase the number (with setInterval) but i also want to increase the number with click on the button. The shown percentages are wild hopping because he shows me an early state and a later state at the same time.
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
setInterval(findWorkRunner, '500');
function findWorkRunner() {
setfindWorkCount(findWorkCount + 1);
if (findWorkCount >= 101) {
setfindWorkCount(0);
}
}
return (
<div className="App">
<button
onClick={() => {
setfindWorkCount(findWorkCount + 11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
When you use an interval in a function component you need to wrap in a useEffect block to ensure that it doesn't create an interval on each render. Since findWorkRunner is a dependency of the useEffect, you need to wrap it in useCallback. You should also use findWorkRunner for the button as well, so the same logic would apply to the button, and the interval updates.
Finally, use a function to update the state, because the updated state is computed using the previous state:
setfindWorkCount(count =>
count + inc >= 101 ? 0 : count + inc
);
Example:
const { useState, useCallback, useEffect } = React;
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
const findWorkRunner = useCallback((inc = 1) => {
setfindWorkCount(count =>
count + inc >= 101 ? 0 : count + inc
);
}, []);
useEffect(() => {
const interval = setInterval(findWorkRunner, '500');
return () => {
clearInterval(interval);
};
}, [findWorkRunner]);
return (
<div className="App">
<button
onClick={() => {
findWorkRunner(11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
ReactDOM
.createRoot(root)
.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
You can fix the issue by using the callback form of setfindWorkCount in the setInterval to ensure the state update happens after the render:
function App() {
const [findWorkCount, setfindWorkCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setfindWorkCount(c => c + 1);
if (findWorkCount >= 101) {
setfindWorkCount(0);
}
}, 500);
return () => clearInterval(intervalId);
}, []);
return (
<div className="App">
<button
onClick={() => {
setfindWorkCount(c => c + 11);
}}
>
Find Job
</button>
<div className="bar">
<div className="fillwork" style={{ width: `${findWorkCount}%` }}>
<div className="counter">{findWorkCount}%</div>
</div>
</div>
</div>
);
}
Set up a setTimeout inside useEffect instead.
useEffect(() => {
const id = setTimeout(() => {
setfindWorkCount(findWorkCount + 1);
if (findWorkCount >= 100) setfindWorkCount(0);
}, 500);
return () => clearTimeout(id);
}, [findWorkCount]);

How to replace react component with another when event triggered in that component

So, i (Total react beginner) have this React component.
import './cryptography.css';
import {useRef} from 'react';
import useInterval from 'react-useinterval';
import { useState } from 'react';
const DisplayTimer = () => {
const [time, setTime] = useState(0);
useInterval(() => {
setTime(time + 1);
}
, 1000);
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
return (
<div className="timer">
<h3>Timer</h3>
<p>{hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}</p>
</div>
)
}
const CryptoOne = () => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "c6e24b99a8054ab24dd0323530b80819") {
alert("Correct!");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-one">
<h3>Level One</h3>
<p>Convert this plaintext into an MD5 hash - "RollingStonesGatherNoMoss"</p>
<input ref={inputRef} type="text" id="one-answer" name="one-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const CryptoTwo = () => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "IaMaHackerManHaHa") {
alert("Correct!");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-two">
<h3>Level Two</h3>
<p>Decode Caesar cipher into plaintext - "FxJxExzhboJxkExEx"</p>
<input ref={inputRef} type="text" id="two-answer" name="two-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const CryptoThree = () => {
const inputRef = useRef();
function verifyAnswer() {
let input = inputRef.current.value;
let answer = "SHA256".toLowerCase();
if (input === answer) {
alert("Correct!, completed the exercise");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-three">
<h3>Level Three</h3>
<p>Identify the type of the hash (Type only the hash type, with no spaces) - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"</p>
<input ref={inputRef} type="text" id="three-answer" name="three-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const Cryptography = () => {
const [active, setActive] = useState("crypto-one");
return (
<div className="cryptography">
<h1>Cryptography</h1>
<DisplayTimer />
<div className="crypto-container">
<CryptoOne />
</div>
</div>
)
}
export { Cryptography };
Here, i have this cryptography function that will display a question and if it is correct, that question will be replaced with another.
I have 3 seperate functions CryptoOne, CryptoTwo and CryptoThree which will be displayed in function Cryptography. So that means if the answer for the question for CryptoOne is correct, then it will be replaced with CryptoTwo and so on. So my questions is HOW DO I DO IT?
Thanks in advance.
ANSWER TO YOUR LAST COMMENT TO HANDLE TIMER
Instead of using react-useinterval you could create a useTimer() hook of your own
import { useEffect, useRef, useState } from "react";
const useTimer = () => {
const [time, setTime] = useState(0);
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
let interval = useRef();
useEffect(() => {
interval.current = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
return () => {
clearInterval(interval.current);
};
}, [setTime]);
const stopTimer = () => {
clearInterval(interval.current);
};
return { hours, minutes, seconds, stopTimer };
};
export default useTimer;
You can maintain a activeIndex state in Cryptography component. And have a changeSlide method which increments activeSlideIndex by 1 everytime the answer is correct. As shown below
TIMER RELATED COMMENT
Use that hook in Cryptography component. Pass hours, minutes & seconds as props in DisplayTimer component. And pass stopTimer function to CryptoThree component & use it when final answer is complete
const Cryptography = () => {
const [active, setActive] = useState("crypto-one");
const [activeIndex, setActiveIndex] = useState(1);
const { hours, minutes, seconds, stopTimer } = useTimer();
const incrementIndex = () => {
setActiveIndex(prevIndex => prevIndex + 1);
};
return (
<div className="cryptography">
<h1>Cryptography</h1>
<DisplayTimer hours={hours} minutes={minutes} seconds={seconds} />
<div className="crypto-container">
{activeSlideIndex === 1 && <CryptoOne changeIndex={incrementIndex} />}
{activeSlideIndex === 2 && <CryptoTwo changeIndex={incrementIndex} />}
{activeSlideIndex === 3 && <CryptoThree stopTimer={stopTimer} />}
</div>
</div>
);
};
const DisplayTimer = ({ hours, minutes, seconds }) => {
return (
<div className="timer">
<h3>Timer</h3>
<p>
{hours.toString().padStart(2, "0")}:
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}
</p>
</div>
);
};
You can pass this changeSlide() method as a prop in every component.
And call it whenever the answer is correct
const CryptoOne = ({ changeIndex }) => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "c6e24b99a8054ab24dd0323530b80819") {
alert("Correct!");
changeIndex();
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-one">
<h3>Level One</h3>
<p>
Convert this plaintext into an MD5 hash - "RollingStonesGatherNoMoss"
</p>
<input ref={inputRef} type="text" id="one-answer" name="one-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};
const CryptoTwo = ({ changeIndex }) => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "IaMaHackerManHaHa") {
alert("Correct!");
changeIndex();
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-two">
<h3>Level Two</h3>
<p>Decode Caesar cipher into plaintext - "FxJxExzhboJxkExEx"</p>
<input ref={inputRef} type="text" id="two-answer" name="two-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};
const CryptoThree = ({stopTimer) => {
const inputRef = useRef();
function verifyAnswer() {
let input = inputRef.current.value;
let answer = "SHA256".toLowerCase();
if (input === answer) {
alert("Correct!, completed the exercise");
stopTimer(); // Use stopTimer here when exercise is complete
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-three">
<h3>Level Three</h3>
<p>
Identify the type of the hash (Type only the hash type, with no spaces)
- "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
</p>
<input ref={inputRef} type="text" id="three-answer" name="three-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};

How to seperate the two functions in ReactJS?

Introduction
I am building the countdown timer, which I had built but now I want to scale it up. So I am building two countdowns on the same page with each of them having separate start, stop buttons, and common reset button.
What do I have?
I have implemented but they are not working independently. For example, when I click on the stop of the upper clock, it also stops the lower clock.
What do I want?
I wanted to have separate functions for both of them but on the same page.
Code
Most of the things are repeated in the code.
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";
const STATUS = {
STARTED: "Started",
STOPPED: "Stopped"
};
const STATUSp1 = {
STARTED: "Started",
STOPPED: "Stopped"
};
export default function InputCountDown() {
const [time, setTime] = useState(0);
const [timep1, setTimep1] = useState(0);
const [inc, setInc] = useState(0);
const handleOnChange = (e) => {
//console.log(e.target.value);
setTime(e.target.value);
setTimep1(e.target.value);
};
const handleOnChangeIncrement = (e) => {
console.log(e.target.value);
setInc(e.target.value);
};
const [secondsRemaining, setSecondsRemaining] = useState(time * 60);
const [secondsRemainingp1, setSecondsRemainingp1] = useState(timep1 * 60);
//console.log(time);
const [status, setStatus] = useState(STATUS.STOPPED);
const [statusp1, setStatusp1] = useState(STATUSp1.STOPPED);
const secondsToDisplay = secondsRemaining % 60;
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60;
const minutesToDisplay = minutesRemaining % 60;
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60;
const secondsToDisplayp1 = secondsRemainingp1 % 60;
const minutesRemainingp1 = (secondsRemainingp1 - secondsToDisplayp1) / 60;
const minutesToDisplayp1 = minutesRemainingp1 % 60;
const hoursToDisplayp1 = (minutesRemainingp1 - minutesToDisplayp1) / 60;
const handleStart = () => {
setStatus(STATUS.STARTED);
setSecondsRemaining(time * 60);
};
const handleStartp1 = () => {
setStatusp1(STATUSp1.STARTED);
setSecondsRemainingp1(timep1 * 60);
};
const handleStop = () => {
setStatus(STATUS.STOPPED);
};
const handleStopp1 = () => {
setStatusp1(STATUSp1.STOPPED);
};
const handleReset = () => {
setStatus(STATUS.STOPPED);
setSecondsRemaining(time * 60);
setSecondsRemainingp1(timep1 * 60);
};
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1);
} else {
setStatus(STATUS.STOPPED);
}
if (secondsRemainingp1 > 0) {
setSecondsRemainingp1(secondsRemainingp1 - 1);
} else {
setStatusp1(STATUSp1.STOPPED);
}
},
status === STATUS.STARTED ? 1000 : null,
statusp1 === STATUSp1.STARTED ? 1000 : null
// passing null stops the interval
);
return (
<>
<div className="App">
<h1>Countdown Using Input</h1>
<div style={{ padding: "12px" }}>
<label htmlFor="time"> Enter time in minutes </label>
<input
type="text"
id="time"
name="time"
value={time}
onChange={(e) => handleOnChange(e)}
/>
</div>
<div style={{ padding: "12px" }}>
<label htmlFor="inc"> Enter increment </label>
<input
type="number"
id="inc"
name="inc"
value={inc}
onChange={(e) => handleOnChangeIncrement(e)}
/>
</div>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div>
<button
onClick={() =>
setSecondsRemaining(secondsRemaining + parseInt(inc, 10))
}
>
Increment {inc} sec
</button>
</div>
<div style={{ padding: 20, fontSize: "40px" }}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
</div>
<div>Status: {status}</div>
<div style={{ padding: 20, fontSize: "40px" }}>
{twoDigits(hoursToDisplayp1)}:{twoDigits(minutesToDisplayp1)}:
{twoDigits(secondsToDisplayp1)}
</div>
<button onClick={handleStartp1} type="button">
Start_p1
</button>
<button onClick={handleStopp1} type="button">
Stop_p1
</button>
<button onClick={handleReset} type="button">
Reset_p1
</button>
</div>
</>
);
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, "0");
You can also find the code in the codesandbox in the inputCountDown.js file .
You can ignore increment button
Please help me !!!
You are using the same interval for two different counters, which causes them to sync up on the same time. Nevertheless, this code is handling too many responsibilities at once and contains unnecessary duplications that is polluting the readability of the code.
Instead of duplicating the same code with different variables names, try extracting it into its own component, and call that component twice. This way, more code isolation is ensured and is definitely less error prone, while improving readability.
Seperate the useInterval logic for each of the timer
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1);
} else {
setStatus(STATUS.STOPPED);
}
},
status === STATUS.STARTED ? 1000 : null
);
useInterval(
() => {
if (secondsRemainingp1 > 0) {
setSecondsRemainingp1(secondsRemainingp1 - 1);
} else {
setStatusp1(STATUSp1.STOPPED);
}
},
statusp1 === STATUSp1.STARTED ? 1000 : null
// passing null stops the interval
);

How to add elements to array on click in react?

Im pretty new to react (i have only worked with classes abit) and I want to add my input values to foodList and write them out on the screen but my brain is locked and i cant figure out how...
Any tips would help, thanks!
import React, {useState, useEffect} from 'react';
const Form = () => {
const [recipe, setRecipe] = useState("");
const [ingrediens, setIngrediens] = useState("");
const [foodList, setFoodList] = useState([])
const handleChange = event => {
setIngrediens({[event.target.name]: event.target.value})
setRecipe({[event.target.name]: event.target.value})
}
const handleClick = event => { // Here is where i get problem
}
return (
<main>
<button onClick={handleClick}>add</button>
<div className="form">
<input type="text" placeholder="Enter your recipe" name="recipe" onChange={handleChange} ></input>
<input type="text" placeholder="Enter your ingrediens" name="ingrediens" onChange={handleChange} ></input>
</div>
<div className="results">
<ul>
{foodList.map(i => (
<li key={i}> {recipe} <p> {ingrediens} </p> </li>
))}
</ul>
</div>
</main>
)
}
export default Form;
I suppose you want something like this? I also refactored other parts of the code, like handleChange, which seemed bit weird.
const Form = () => {
const [recipe, setRecipe] = useState("");
const [ingrediens, setIngrediens] = useState("");
const [foodList, setFoodList] = useState([]);
const handleChangeRecipe = event => {
setRecipe(event.target.value);
};
const handleChangeIngredients = event => {
setIngrediens(event.target.value);
};
const handleClick = event => {
setFoodList([...foodList, { recipe: recipe, ingrediens: ingrediens }]);
};
console.log(foodList);
return (
<main>
<button onClick={handleClick}>add</button>
<div className="form">
<input
type="text"
placeholder="Enter your recipe"
name="recipe"
onChange={handleChangeRecipe}
/>
<input
type="text"
placeholder="Enter your ingrediens"
name="ingrediens"
onChange={handleChangeIngredients}
/>
</div>
<div className="results">
<ul>
{foodList.map((x, i) => (
<li key={i}>
{" "}
{x.recipe} <p> {x.ingrediens} </p>{" "}
</li>
))}
</ul>
</div>
</main>
);
};
you need to update the foodList state hook with the new array:
const handleClick = event => {
setFoodList((_foodlist) => [..._foodlist, { new element values }]);
}
That's pretty much it, if you update the state, the component will re-render and show the updated foodList.
EDIT #1:
I used the callback way inside the setFoodList so that there is no race condition if the user clicks the button very fast. It's an edge case but a nice to have.
export default function App() {
const [time, setTime] = React.useState(0);
const [start, setStart] = React.useState(false);
let [laps, setLaps] = React.useState([]);
React.useEffect(() => {
let interval = null;
if (start) {
interval = setInterval(() => {
setTime((t) => t + 10);
}, 10);
} else {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [start]);
let m = Math.floor((time / 60000) % 60);
let s = Math.floor((time / 1000) % 60);
let ms = Math.floor((time / 10) % 100);
m < 10 ? (m = '0' + m) : m;
s < 10 ? (s = '0' + s) : s;
ms < 10 ? (ms = '0' + ms) : ms;
let stop_watch = `${m}:${s}:${ms}`;
const lapHandel = () => {
setLaps([...laps, stop_watch]);
};
console.log(laps);
}

My Balance States Throws Balance.map is not a function error?

I'm making a simple React app that keeps track of expenses, income, and balances. I'm using a state hook called Balance that is the sum of all the input state. The issue is React throws a Balance.map is not a function. So, I'm unable to show what the total would be when the user enters all their input.
Here's the full code:
export default function App() {
const [Balance, setBalance] = useState([]);
const [Income, setIncome] = useState(0);
const [Expense, setExpense] = useState(0);
const [Input, setInput] = useState([]);
console.log(typeof Balance);
const handleChange = (e) => {
setInput(e.target.value);
};
const handleClick = (e) => {
e.preventDefault();
if (Input > 0) {
setIncome(Input);
} else {
setExpense(Input);
}
let val = parseInt(Input);
setBalance(val, ...Balance);
//console.log(...Balance);
setInput("");
};
return (
<div style={{ textAlign: "center", marginTop: "150px" }}>
<form>
<label htmlFor="input"> Please enter value: </label>
<input
name="input"
type="number"
value={Input}
onChange={handleChange}
/>
<button onClick={handleClick}> Submit</button>
</form>
<br />
<h3> My Balance:</h3>
<p>
{Balance.map((i) => {
return i;
})}
</p>
<h3> My Income: {Income}</h3>
<h3> My Expense: {Expense}</h3>
</div>
);
}
I know that the Balance state ,despite it being set as an array, is still an object. I'm assuming the error is coming from there? If so, should setBalance(...Input) work?
as users say in the comments, the problem is the way you update the value of Balance, Here I do some changes in this places:
let val = parseInt(Input, 10); // Here I add the base of parseInt
setBalance([...Balance, val]) Here I let the value as an array in the setBalance
The complete code is:
import React, {useState} from "react";
import "./styles.css";
export default function App() {
const [Balance, setBalance] = useState([]);
const [Income, setIncome] = useState(0);
const [Expense, setExpense] = useState(0);
const [Input, setInput] = useState([]);
console.log(typeof Balance);
const handleChange = (e) => {
setInput(e.target.value);
};
const handleClick = (e) => {
e.preventDefault();
if (Input > 0) {
setIncome(Input);
} else {
setExpense(Input);
}
let val = parseInt(Input, 10);
setBalance([...Balance, val]);
//console.log(...Balance);
setInput("");
};
return (
<div style={{ textAlign: "center", marginTop: "150px" }}>
<form>
<label htmlFor="input"> Please enter value: </label>
<input
name="input"
type="number"
value={Input}
onChange={handleChange}
/>
<button onClick={handleClick}> Submit</button>
</form>
<br />
<h3> My Balance:</h3>
<p>
{Balance.map((i) => {
return i;
})}
</p>
<h3> My Income: {Income}</h3>
<h3> My Expense: {Expense}</h3>
</div>
);
}

Categories

Resources