I have two problems:
the conditional rendering is not working well on Statistics component
inline IF on the line 46 didn't work so I commented out. I checked the values with typeof jc func and all values are numbers
Please let me know if you spot what I've done wrong.
Thanks a lot!
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
const Feedback = (props) => (
<button onClick={props.handleFeedback}>{props.text}</button>
)
const Statistics = (props) => {
console.log(props);
if (props.total.length === 0) {
return (
<div>empty</div>
)
}
return (
<div>
<p>all {props.total}</p>
<p>average {props.average}</p>
<p>positive {props.positive}</p>
</div>
)
}
const App = () => {
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const setToGood = (newValue) => () => {
setGood(newValue)
}
const setToNeutral = (newValue) => () => {
setNeutral(newValue)
}
const setToBad = (newValue) => () => {
setBad(newValue)
}
const total = good + neutral + bad;
const average = (good - bad) / total
const positive = (good / total * 100 === 0 ) ? '' : (good / total * 100)
console.log(positive);
return (
<div>
<h1>give feedback</h1>
<Feedback handleFeedback={setToGood(good +1)} text="good" />
<Feedback handleFeedback={setToNeutral(neutral +1)} text="neutral" />
<Feedback handleFeedback={setToBad(bad +1)} text="bad" />
<h1>statisctics</h1>
<p>good {good}</p>
<p>neutral {neutral}</p>
<p>bad {bad}</p>
<Statistics total={total} average={average} positive={positive}/>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
Inside IF don't use props.total.length as total is of number type not string.
Related
Is there a way I can generate multiple SET VALUES per second using react?
What am I trying to accomplish?
I have created this "tap game" where you could generate $1 per tap up to $250 per tap depending on where you tap. In this game I want to be able to "hire employees." After designating the hired employee to, lets say "$1 team", my "Score" will increase by 1 per second. If I decided to hire another employee and I decided to assign it to "$5 team", it would increase by 5. Since I have 2 employees at this time, I do not want to cancel one or the other out, I would like them to run simultaneously (e.g. 6 per second in this example).
What Have I tried?
I have not tried, or tested, anything specifically because of course I am unsure of how to accomplish this. I have researched other Stack Overflow examples and I have came across one very similar, however, they are using an API where as I am not. Stack Overflow Example I am not sure if I would even need an API, I would assume I would not?
Request:
If at all possible, a solution that I could use dynamically would be extremely appreciated. If I decided to hire 10 employees and stick them all on $1 team, I would like to be able to do so.
Attached is a fully functional code.
import React, {useState, useEffect} from 'react'
const App = () => {
// ======================================
// HOOKS
// ======================================
const [score, setScore] = useState(0)
const [showEZBake, setShowEZBake] = useState(false)
const [showToasterOven, setShowToasterOven] = useState(false)
const [showConvectionOven, setShowConvectionOven] = useState(false)
const [showSmallFactory, setShowSmallFactory] = useState(false)
// ======================================
// FUNCTIONS
// ======================================
const winCondition = () => {
if (score >= 100000) {
return (
<h1>YOURE A WINNER!!</h1>
)
}
}
// EARN REVENUE FUNCTIONS
const earn1 = () => {
setScore(score + 1)
winCondition()
}
const earn5 = () => {
setScore(score + 5)
winCondition()
}
const earn25 = () => {
setScore(score + 25)
winCondition()
}
const earn50 = () => {
setScore(score + 50)
winCondition()
}
const earn250 = () => {
setScore(score + 250)
winCondition()
}
// PURCHASE ITEMS FUNCTIONS
const buyEZOven = () => {
setScore(score - 25)
}
const buyToasterOven = () => {
setScore(score - 250)
}
const buyConvectionOven = () => {
setScore(score - 1000)
}
const buySmallFactory = () => {
setScore(score - 15000)
}
const upgradeEZOven = () => {
if (score >= 25) {
setShowEZBake(true)
buyEZOven()
}
}
const upgradeToasterOven = () => {
if (score >= 250 ) {
setShowToasterOven(true)
buyToasterOven()
}
}
const upgradeConvectionOven = () => {
if (score >= 1000) {
setShowConvectionOven(true)
buyConvectionOven()
}
}
const upgradeSmallFactory = () => {
if (score >= 15000) {
setShowSmallFactory(true)
buySmallFactory()
}
}
// ======================================
// DISPLAY
// ======================================
return (
<div>
<h1>Bakery</h1>
<h2>Revenue {score}</h2>
<h3>No Bake Pudding</h3><button onClick={earn1}>$1</button>
{/* EZ BAKE OVEN */}
{showEZBake ? (
<>
<h3>Easy Bake Oven</h3>
<button onClick={earn5}>$5</button>
</>
) : (
<>
<h3>Purchase Easy Bake Oven</h3>
<button onClick={upgradeEZOven}>$25</button>
</>
)
}
{/* TOASTER OVEN */}
{showToasterOven ? (
<>
<h3>Toaster Oven</h3>
<button onClick={earn25}>$25</button>
</>
) : (
<>
<h3>Purchase Toaster Oven</h3>
<button onClick={upgradeToasterOven}>$250</button>
</>
)}
{/* CONVECTION OVEN */}
{showConvectionOven ? (
<>
<h3>Convection Oven</h3>
<button onClick={earn50}>$50</button>
</>
) : (
<>
<h3>Purchase Convection Oven</h3>
<button onClick={upgradeConvectionOven}>$1000</button>
</>
)}
{/* FACTORY */}
{showSmallFactory ? (
<>
<h3>Small Factory Production</h3>
<button onClick={earn250}>$250</button>
</>
) : (
<>
<h3>Purchase Small Factory</h3>
<button onClick={upgradeSmallFactory}>$15,000</button>
</>
)}
{/* WIN CONDITION */}
{
winCondition()
}
</div>
)
}
export default App
You can generate a value every second pretty easily with useEffect:
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setCount(count + 1);
}, 1000);
return () => clearTimeout(timer);
}, [count, setCount]);
I built a cool little audio player and am having issues with data fetching. The page renders before the audio file in the return statement src, here:
<audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio>
The NaN shows up in the duration time represented by this line:
{/* duration */}
<div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>
This above line of code isn't preventing the NaN, so I tried my hand at fetching in the useEffect, shown below but that has made this issue worse.
const [data, setData] = useState([])
--------------------
useEffect(() => {
fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
res => setData(res.loadedmetadata)
)
})
--------------------
<audio ref={audio} src={data} alt="oops, something went wrong..."></audio>
If anyone could give it a look and point me in the right direction, id be very grateful. Below I will provide all the code for my component.
import React, { useState, useRef, useEffect } from 'react';
import styles from '../styles/AudioPlayer.module.css';
import {BsArrowClockwise} from 'react-icons/bs';
import {BsArrowCounterclockwise} from 'react-icons/bs';
import {BsPlayCircleFill} from 'react-icons/bs';
import {BsPauseCircleFill} from 'react-icons/bs';
const AudioPlayer = () => {
//state
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [data, setData] = useState([])
//refs
const audio = useRef();
const progressBar = useRef();
const progressBarAnimation = useRef();
//effects
useEffect(() => {
const seconds = Math.floor(audio.current.duration);
setDuration(seconds);
progressBar.current.max = seconds;
}, [ audio?.current?.loadedmetadata, audio?.current?.readyState ]);
useEffect(() => {
fetch("https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg").then(
res => setData(res.loadedmetadata)
)
})
//functions & Handlers
const calculateTime = (secs) => {
const minutes = Math.floor(secs / 60);
const returnedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
const seconds = Math.floor(secs % 60);
const returnedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
return `${returnedMinutes}:${returnedSeconds}`;
}
const isPlayingHandler = () => {
const prevValue = isPlaying;
setIsPlaying(!prevValue);
if (!prevValue) {
audio.current.play();
progressBarAnimation.current = requestAnimationFrame(whilePlaying);
} else {
audio.current.pause();
cancelAnimationFrame(progressBarAnimation.current);
};
};
const whilePlaying = () => {
progressBar.current.value = audio.current.currentTime;
progressBarValueTicker();
progressBarAnimation.current = requestAnimationFrame(whilePlaying);
};
const progressHandler = () => {
audio.current.currentTime = progressBar.current.value;
progressBarValueTicker();
};
const progressBarValueTicker = () => {
progressBar.current.style.setProperty('--seek-before-width', `${progressBar.current.value / duration * 100}%`);
setCurrentTime(progressBar.current.value);
}
const backwardFifteen = () => {
console.log(progressBar.current.value)
progressBar.current.value = Number(progressBar.current.value) - 15;
console.log(progressBar.current.value)
progressHandler();
};
const forwardFifteen = () => {
console.log(progressBar.current.value)
progressBar.current.value = Number(progressBar.current.value) + 15;
console.log(progressBar.current.value)
progressHandler();
};
return(
<>
<div>
{/* eventually, a loop component tag will replace the below line to loop all audio file title and descriptions*/}
</div>
<div className={styles.audioWrapper}>
{/* eventually, a loop component tag will replace the below line to loop all audio files*/}
<audio ref={audio} src={data} alt="oops, something went wrong..."></audio>
{/* <audio ref={audio} src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg" alt="oops, something went wrong..."></audio> */}
<button className={styles.sideButtons} onClick={backwardFifteen}><BsArrowCounterclockwise />15</button>
<button className={styles.playPauseButton} onClick={isPlayingHandler}>
{ isPlaying ? <BsPauseCircleFill /> : <BsPlayCircleFill /> }</button>
<button className={styles.sideButtons} onClick={forwardFifteen}>15<BsArrowClockwise /></button>
{/* current time */}
<div className={styles.currentTime}>{calculateTime(currentTime)}</div>
{/* progress bar */}
<div>
<input type="range" ref={progressBar} className={styles.progressBar} onChange={progressHandler} defaultValue='0'/>
</div>
{/* duration */}
<div className={styles.duration}>{(duration && !isNaN(duration)) && calculateTime(duration)}</div>
</div>
</>
);
};
export default AudioPlayer;
const onLoadedMetadata = ()=>{
const seconds = Math.floor(audioPlayer.current.duration);
setDuration(seconds);
progressBar.current.max = seconds;
}
<audio ref={audioPlayer} src={audio_file} preload="metadata" onLoadedMetadata={onLoadedMetadata}></audio>
The reason the NaN is rendered is because NaN is a falsy value and will return the value immediately in the expression below.
(duration && !isNaN(duration)) && calculateTime(duration)
// `NaN && !isNaN(NaN)` returns `NaN` because it is falsy
Simply removing the first condition will avoid NaN being rendered.
!isNaN(duration) && calculateTime(duration)
However, the actual duration value will still be NaN and nothing will get rendered. This is because when you check for the audio.current.duration value inside the useEffect the duration hasn't actually updated yet.
To solve this issue, you can listen to the onDurationChange event in the audio element and update the duration state variable when it gets triggered.
// Convert the `useEffect` code into a function instead
const onDurationChangeHandler = (e) => {
const seconds = Math.floor(e.target.duration);
setDuration(seconds);
progressBar.current.max = seconds;
};
<audio
ref={audio}
src="https://dl.dropbox.com/s/wfhmtvbc5two1wa/1-allen_2991.ogg"
alt="oops, something went wrong..."
onDurationChange={onDurationChangeHandler}
></audio>
I want to subtract the value of 'percent' from the function by 0.25.
However, subtraction does not work.
I used setState, but I don't know why it doesn't work.
import React, {useState, useRef, useCallback} from 'react';
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback(() =>{
if (intervalRef.current !== null){
return;
}
intervalRef.current = setInterval(()=>{
if (percent > 0){
setPercent(c => c - 0.25);
console.log("percent = ", percent);
}
else {
setPercent(c => 1);
}
}, 1000);
}, []);
return (
<div>
<button onClick={()=>{start()}}>{"Start"}</button>
</div>
);
}
export default Ques;
Issue
The enqueued state updates are working correctly but you've a stale enclosure over the percent state in the interval callback that you are logging, it never will update.
Solution
If you want to log the percent state then use an useEffect hook to log changes.
const Ques = () => {
const [percent, setPercent] = useState(1);
const intervalRef = useRef(null);
useEffect(() => {
console.log("percent = ", percent); // <-- log state changes here
}, [percent]);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setPercent((c) => Math.max(0, c - 0.25)); // <-- simpler updater function
}, 1000);
}, []);
return (
<div>
Percent: {percent * 100}
<button onClick={start}>Start</button>
</div>
);
};
You can create a ref for percent also and chenge its current value as:
codesandbox link
import React, { useRef, useCallback } from "react";
const Ques = () => {
const percentRef = useRef(1);
const intervalRef = useRef(null);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
console.log("percent = ", percentRef.current);
percentRef.current > 0
? (percentRef.current -= 0.25)
: (percentRef.current = 1);
}, 1000);
}, []);
return (
<div>
<button onClick={start}>Start</button>
</div>
);
};
export default Ques;
I think useCallback and useRef is not a good fit. Below is a minimal verifiable example using useState and useEffect. Note this function appropriately performs cleanup on the timer when the component is unmounted. Click Run to run the code snippet and click start to begin running the effect.
function App() {
const [percent, setPercent] = React.useState(1)
const [running, setRunning] = React.useState(false)
React.useEffect(() => {
if (!running) return
const t = window.setTimeout(() => {
setPercent(c => c > 0 ? c - 0.25 : 1)
}, 1000)
return () => window.clearTimeout(t)
}, [running, percent])
return <div>
<button onClick={() => setRunning(true)} children="start" />
<pre>percent: {percent}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
This may be one possible solution to achieve what is presumed to be the desired objective:
Code Snippet
const {useState, useRef, useCallback} = React;
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback((flag) => {
if (intervalRef.current !== null){
if (flag && flag === 'end') clearInterval(intervalRef.current);
return;
}
intervalRef.current = setInterval(() => {
setPercent(
prev => (prev > 0 ? prev - 0.25 : 1)
);
}, 1000);
}, []);
return (
<div>
percent: {percent} <br/> <br/>
<button onClick={() => start('bgn')}>Start</button>
<button onClick={() => start('end')}>Stop</button>
</div>
);
}
ReactDOM.render(
<div>
<h3>DEMO</h3>
<Ques />
</div>,
document.getElementById('rd')
);
<div id='rd' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
There are two buttons Start and Stop
Both invoke the same start method, but with different params (flag)
If intervalRef is already set (ie, not null) and flag is end, clear the interval
The percent is added to the UI to see real-time changes to its value
setPercent is modified to use prev (which holds the correct state)
Following suggestions from You (StackOverflow), I have rewritten my app to [several components]https://codesandbox.io/s/points-scored-forked-brnni?file=/src/components/ScoredPointsList.js:0-561
Now when I run yarn start, I get a reference error. After googling, I have tried with "everything" including:
changed from npm to yarn, changed in package.json ... but nothing seems to help.
How can I change the code to make it work?
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers = [...players].sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
const onePointHandler = () => {
// eslint-disable-next-linex
const players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit ? {...player, totPoints: player.totPoints + 1} : player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1,
};
setPlayers([...players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber('');
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
Thanks in advance
Regards
Peter
You are trying to modify players variable before it is being initialized , you can useMemo(which runs only if players value change) and modify sortPlayers after it is available and also you were trying to initalize players again that was causing issue
const sortedPlayers =useMemo(()=>{
return players.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
Full Code:
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
import {useMemo} from 'react'
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers =useMemo(()=>{
return players?.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
const onePointHandler = () => {
const _players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = _players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit
? { ...player, totPoints: player.totPoints + 1 }
: player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1
};
setPlayers([..._players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber("");
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
refer sandbox:
I have a function which returns the react content (JSX.Element) in the if statement. But it does not seem to work. I only get the first statement to trigger
I have recreated the problem with codesandbox with a simple example. To test this just keep pressing the button and view the console.log. to see if the rndType has changed. It should show the other heading in that case, but it doesnt
export default function App() {
const [number, setNumber] = useState({ rnd: 100 });
const [name, setName] = useState({ rnd: "Name" });
const type = ["Number", "Text"];
const getContent = () => {
let rndNo = Math.floor(Math.random() * type.length);
let rndType = type[rndNo];
console.log("rndType", rndType);
if (rndType === "Number") {
return (
<>
<h1>{number.rnd}</h1>
</>
);
} else {
return (
<>
<h1>{name.rnd}</h1>
</>
);
}
};
return (
<div className="App">
{getContent()}
<button onClick={() => getContent()}>Click Me!</button>
</div>
);
}
component will only rerender on state update, and you don't update any. you can store the selected type in a state then it would work as expected:
export default function App() {
const [number, setNumber] = useState({ rnd: 100 });
const [name, setName] = useState({ rnd: "Name" });
const [type, setType] = useState("Text");
const types = ["Number", "Text"];
const getContent = () => {
const content = type === "Number" ? number.rnd : name.rnd
return <h1>{content}</h1>
};
const setRandomType = () => {
let rndNo = Math.floor(Math.random() * types.length);
let rndType = types[rndNo];
setType(rndType);
}
return (
<div className="App">
{getContent()}
<button onClick={setRandomType}>Click Me!</button>
</div>
);
}