React useState does not update after new array was assigned - reactjs

I am trying to build a sorting visualizer in React.
Unfortunately, my useState doesn't update its value after a random array gets generated.
But in an earlier project it worked just fine.
I tried different ways to solve this issue but none of them worked.
import React, { useState } from "react";
// import "./SortingVisualizer.css";
export default function SortingVisualizer() {
const [numberArray, setNumberArray] = useState(null);
function resetArray() {
const array = [];
for (let i = 0; i < 100; i++) {
let num = randomIntFromInterval(5, 1000);
array.push(num);
console.log(array);
}
setNumberArray(array);
}
if (numberArray === null) resetArray();
return (
<div className="array-container">
{numberArray.map((value, idx) => {
return (
<div className="array-bar" key={idx}>
{value}
</div>
);
})}
</div>
);
}
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}

Related

React useState from useEffect's loop works only once when was expected twice [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 3 months ago.
Working on my project I faced this kind of problem and tried to solve it on completely clean vite react project but it is still here.
import { useEffect, useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
const [deck, setDeck] = useState<number[]>([1,2,3,4,5])
function drawCard() {
let tmpDeck = deck
let randomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
tmpDeck = tmpDeck.filter(card => card !== randomCard)
return tmpDeck
}
useEffect(() => {
if(count >= 1) {
for(let i = 0; i < 2; i++){
let tmpDeck = drawCard()
setDeck(tmpDeck)
console.log(deck);
}
}
}, [count])
return (
<div>
<div className="card">
<button onClick={() => {setCount(count+1)}}>
test
</button>
</div>
<p>
{deck}
</p>
</div>
)
}
export default App
My goal is to delete 2 elements from a deck by clicking the button. By increasing count I expecting useEffect to call once and with for loop deleting 2 random elements. I tried doing things like console.log in the loop and it prints twice as expected. Than I tried console.log in the function and it prints twice as well. But when it comes to setDeck() from function or setDeck() from useEffect's loop it happens only once.
Set state functions are async so the changes are not reflected immediately.
You can fix your issue by deleting 2 items at once like this:
function drawCards() {
let tmpDeck = [...deck]
let firstRandomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
let secondRandomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)]
tmpDeck = tmpDeck.filter(card => card !== firstRandomCard && card !== secondRandomCard);
return tmpDeck;
}
useEffect(() => {
if(count >= 1) {
let tmpDeck = drawCards()
setDeck(tmpDeck)
console.log(tmpDeck);
}
}, [count])
Update:
If you want to use loop, this solution will help you:
function drawCard(tempDeck) {
const tmpDeck = tempDeck ? [...tempDeck] : [...deck];
const randomCard = tmpDeck[Math.floor(Math.random() * tmpDeck.length)];
return tmpDeck.filter(card => card !== randomCard);
}
useEffect(() => {
if(count >= 1) {
let tempDeck = [...deck];
for(let i = 0; i < 2; i++){
tempDeck = drawCard(tempDeck);
}
setDeck(tempDeck);
}
}, [count]);

Duplicated setTimeout

When I press the "Buy" button sometimes its grow up correctly and sometimes its just count by 2 clicks. Why this is happening? I am using UseState for change the values and setTimeout for passive increment, so when I press the Buy button its remove the older setTimeout and add a new with the new values.
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import React, { useState, useEffect } from 'react';
var increment = 1000;
var preco = 10;
var total = 0;
function Master() {
const [points, setpoints] = useState(0);
const [Ppoints, setPpoints] = useState(0);
total = points;
function addpoints() {
setpoints((points) => points + 1);
window.clearTimeout(timer);
}
function addPpoints() {
setpoints((points) => points + Ppoints);
}
//passive addiction
let timer = setTimeout(addPpoints, increment);
function passive() {
if (total >= preco) {
setPpoints(Ppoints + 0.5);
setpoints(points - preco);
preco += 10;
window.clearTimeout(timer);
}
}
return (
<>
<h1>{total}</h1>
<button onClick={addpoints}>adicionar</button>
<h1>Price: {preco}</h1>
<button onClick={passive}>Buy</button>
</>
);
}
export default function Home() {
return (
<>
<Master />
</>
);
}

I want to create an array of non-overlapping random numbers

I want to create an array of non-overlapping random numbers with a length of 20.
However, when executed, the array is not complete and sometimes an error message "Too many re-renders" appears.
import React, {useState, useEffect} from "react";
const Ques = () => {
const [randArr, setRandArr] = useState([]);
const [randNum, setRandNum] = useState(0);
let rand = Math.floor(Math.random() * (19-0));
if(randArr.length !== 20 && randArr.includes(randNum) === true){
rand = Math.floor(Math.random() * (19-0));
setRandNum(rand)
}
useEffect(() =>{
setRandNum(rand);
setRandArr([...randArr, randNum]);
console.log("randNum : ", randNum);
console.log("randArr : ", randArr);
},[rand]);
return (
<div>
<button>Start</button>
</div>
);
};
export default Ques;
As has been pointed out in comments, you are declaring a new rand value each render cycle and using it as a dependency for an useEffect hook which then also enqueues a state update and triggers a rerender... repeat ad nauseam.
Instead of trying to iterate and populate an array with "random" numbers by "guessing" if they've already been selected, it'd be better to start with an array of [1..20] and "shuffle" it.
Example:
const res = Array.from({ length: 20 }, (_, i) => i + 1).sort(() => Math.random() - 0.5);
console.log(res.join(","));
You can just initialize your state to this value. No need for any loops and useEffect hooks.
const [randArr, setRandArr] = useState(
Array.from({ length: 20 }, (_, i) => i + 1).sort(() => Math.random() - 0.5)
);
import React, { useState, useEffect } from "react";
const App = () => {
const [random, setRandom] = useState([]);
const getRandom = () => Math.floor(Math.random() * (19 - 0));
useEffect(() => {
const arr = [];
for (let i = 0; i < 20; i++) {
arr.push(getRandom());
}
setRandom(arr);
console.log(arr);
}, []);
return (
<div>
<div>{random.join(",")}</div>
<button>Start</button>
</div>
);
};
export default App;

How to score after question is answered? React js

I'm trying to score points after the answer is answered correctly or wrongly. Possibly is something easy to see but I'm still new to react ... sorry;/ I will appreciate any help.
const handleOption = () => {
if (isClick[countQuestion - 1]) return;
let newdata = [...isClick];
newdata[countQuestion - 1] = true;
setIsClick(newdata);};
<div className="options-display">
{questions.options.map((options) => {
return (
<button
className={`btn-option ${
isClick[countQuestion - 1]
? options === questions.answer ? "correct" : "incorrect"
:""
}`}
key={options}
onClick={handleOption}
>
{options}
</button>
);
})}</div>
export default Question;
And that's my sec component=child
import React, {useState} from 'react'
function Score(){
const [score,setScore]=useState(0)
return (
<div>
<h1>Your score is: {score}</h1>
</div>)}
export default Score;
You should use a hook called useEffect to run code after a component has been rendered. Here's an example of how to use it:
import React, { useEffect } from 'react';
function Score() {
const [score, setScore] = useState(0);
useEffect(() => {
if (isClick[countQuestion - 1]) {
let newScore = score + 1;
setScore(newScore);
}
}, [isClick]);
return (
<div>
<h1>Your score is: {score}</h1>
</div>
);
}

React Error: Hooks can only be called inside the body of a function component

I know this is very common error Invariant Violation: Hooks can only be called inside the body of a function component but still I am not able to figure it out how to fix it in my code.
What I understood is the issue is with useMemo hook in my code ? please guide me. As per my understanding, problem is mismatching versions of React and ReactDOM or it is related to I am not calling my hooks properly. Parent component is using react 15 and child component's code refer react 16.8 code.
Problem is with specific to this code -
/**
* Get the difference from parseDiff in a formatLines
*/
const diff = useMemo(() => {
const diffText = formatLines(
diffLines(props.startupConfigData, props.runningConfigData),
{
context: 3
}
);
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
return diff;
}, [props.startupConfigData, props.runningConfigData]);
const { type, hunks } = diff;
/**
* Token used for useMemo
*/
const tokens = useMemo(() => tokenize(hunks), [hunks]);
My full component code -
import React, { useMemo } from "react";
import { diffLines, formatLines } from "./unidiff";
import { parseDiff, Diff, Hunk } from "react-diff-view";
import "./diff.less";
import tokenize from "./tokenize";
import { DnxLoader } from "*external*/#cisco-dna/dnx-react-components";
import i18n from "amdi18n-loader!~/nls/i18_compliance";
import { labels } from "../constants";
import { getStatusComplianceType } from "../common";
const DiffViewer = props => {
const { compareWith } = props;
/**
* Get the difference from parseDiff in a formatLines
*/
const diff = useMemo(() => {
const diffText = formatLines(
diffLines(props.startupConfigData, props.runningConfigData),
{
context: 3
}
);
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
return diff;
}, [props.startupConfigData, props.runningConfigData]);
const { type, hunks } = diff;
/**
* Token used for useMemo
*/
const tokens = useMemo(() => tokenize(hunks), [hunks]);
/**
* Get text based on selection - startup/running
*/
const getStartupText = () => {
if (compareWith == undefined || compareWith === "startup") {
return i18n.label_startup_configuration;
} else {
return i18n.label_running_configuration;
}
};
/**
* Get date format
* #param {*} date
*/
const getFormattedDate = date => {
var lastUpdateTimeData;
const notAvailable = i18n.label_non_applicable;
if (date !== null) {
lastUpdateTimeData = moment(date).format(labels.lastUpdateTimeFormatAmPm);
} else {
lastUpdateTimeData = notAvailable;
}
return lastUpdateTimeData;
};
const statusDisplayDetails = getStatusComplianceType(
"COMPLIANT",
true
);
/**
* return function
*/
return (
<div className="margin-left-right">
{hunks ? (
<div>
<div className="flex">
<div className="startup-text">
{getStartupText()}
<div className="font-size-10">
{i18n.label_no_of_lines}:{" "}
{props.startUpFile.totalNoOfLines}
</div>
<div className="font-size-10">{i18n.label_archived_on}: {getFormattedDate(props.startUpFile.createdTime )}</div>
</div>
<div className="running-text">
{i18n.label_running_configuration}
<div className="font-size-10">
{i18n.label_no_of_lines}:{" "}
{props.runningFile.totalNoOfLines}
</div>
<div className="font-size-10">{i18n.label_archived_on}: {getFormattedDate(props.runningFile.createdTime)}</div>
</div>
</div>
{ Array.isArray(hunks) && hunks.length ? (
<div className="diff-div">
<Diff
viewType="split"
diffType={type}
hunks={hunks}
tokens={tokens}
>
{hunks =>
hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)
}
</Diff>
</div>
) : (
<div className="div-margin-diff">
{statusDisplayDetails}
<span>{compareWith === "running" ? i18n.label_running_config_compliant : i18n.label_running_startup_config_compliant}</span>
</div>
)
}
</div>
) : (
<div className="loading-icon">
<DnxLoader color="#026E97" size="54" label="Loading..." />
</div>
)}
</div>
);
};
export default DiffViewer;
Can someone please point what is the problem ?

Resources