Update DOM elements when state is updated using setTimeout() React - javascript

I have the following code in React. This code uses a time expensive function that performs some coputations over a large number of data. in the begining the state is filled with an array that says no computations. When the button is pressed, I want that array of the state to be filled with waiting, and after each iteration over i is complete, the element on potition i to be 'Done'. The problem is that by using set timeout, the DOM elements are not updating untill the whole function is over. Ignore the computations in the for, they are meaningless in this example. To have an ideea the function performs polynomial fit over the given data. My problem is how can I update the Dom elements while the function is running, So i can knwow which data is done and which is computing in real time
const Component = ( {data1, data2} ) => {
const [state, setState] = useState(Array(data1.length).fill('No Computations'))
const timeExpensiveFunction = () => {
// data1.length = 10
let variable0
let variable1
let variable2
let variable3
for(let i = 0; i< data1.length; i++){
//data1[i].length = 40 000
for(let j = 0; j< data1[i].length; j++){
variable1 += data1[i][j]
variable2 += variable1*data2[i][j]**3
variable3 += data2[i][j]**2
variable1 += data1[i][j]*data2[i][j]
}
setTimeout(() => {
setState(state => {
return[
...state.slice(0,i),
'Done',
...state.slice(i+1),
]
})
},1000)
}
return([variable1,variable2,variable3,variable4])
}
const randomFunc = (e) => {
setTimeout((e) => setState(Array(data1.length).fill('Waiting ...')),1000)
timeExpensiveFunction() //Duration depends on the number of data, it is usually higher that 10-15 seconds
}
return (
<div>
<div>
{state.map((element) => (
{element} //This does not update with the function setTimeout. It sets after the expensive computation is over
))}
</div>
<div>
<button onClick = {(e) => randomFunc(e)}>press</button>
</div>
</div>
)
}

First of all, You must use useState and useEffect to set a state and re-render the DOM.
You can use like below:
import { useState, useEffect } from 'react'
const Component = () => {
const [state, setState] = useState([1,2,3,4,5,6,7,8,9,10])
const [isPress, setPress] = useState(false); // use it to not call a method on first render
const timeExpensiveFunction = () => {
// lot of work here
}
const randomFunc = (e) => {
setState([10,9,8,7,6,5,4,3,2,1])
setPress(true)
}
useEffect({
if (isPress) setTimeout(() => timeExpensiveFunction(),1000)
}, [state])
return (
<div>
<div>
{state.map((element) => (
{element}
))}
</div>
<div>
<button onClick = {(e) => randomFunc(e)}>press</button>
</div>
</div>
)
}

Related

How can I pass data from a React component to main file?

I am having trouble passing data from component to main file in react. It is a basic quiz application, get quiz objects from api and pass data to the "Question" component. In the main file i have score state to decide how many answers are correct. But when answer button clicked application see value in the component. I want to match answer value and correct answer and increase score by state. Here is the Question component and main file.
export default function Question(props) {
const [flag,setFlag] = React.useState(true)
// TOGGLE BUTTON BACKGROUND COLOR WHEN BTN CLICKED
function toggleBtnBgColor(btn) {
if(flag) {
btn.target.classList.toggle("dark-bg-color")
setFlag(false)
}
}
return (
<>
<div className="question" key={props.index}>{props.question}</div>
{props.answers.map((answer,index)=> {
return (
<button key={index} onClick={toggleBtnBgColor} className="answer-button">{answer}</button>
)
})}
<hr></hr>
</>
)
}
Main file;
const [data, setData] = React.useState([])
const [score, setScore] = React.useState(0)
const [startAgain, setStartAgain] = React.useState(false)
// FETCH QUIZ DATA FROM API
const fetchData = async () => {
await fetch("https://opentdb.com/api.php?amount=5&type=multiple")
.then((response) => response.json())
.then((data) => setData(data.results))
}
React.useEffect(()=> {
fetchData()
},[])
// SHUFFLE ALGORITHM TO USE LATER
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
// RENDER QUESTIONS
const questionElements = data.map((question, index) => {
// ANSWERS ARRAY AND SHUFFLE
let answers = [...question.incorrect_answers,question.correct_answer]
shuffle(answers)
return (
<Question key={index} index={index} question={question.question} answers={answers}/>
)
})
// CALCULATE FINAL SCORE WHEN CHECK BTN CLICKED
function calcScore() {
for(let question of questionElements) {
console.log(question.calcScore)
}
setStartAgain(true)
}
return (
<div className="question-container">
{questionElements}
<p>{startAgain && ("Your score is "+ score +"/5")}</p>
<button onClick={calcScore} className="check-button">{!startAgain ? "Check Answers" : "Restart Quiz"}</button>
</div>
)
Check this out, we pass a function to Question that receives the answer and index. The parent will update an array that carries all the answers
https://codesandbox.io/s/black-cdn-p6lo5v?file=/src/App.js
const setAnswer = (index, answer) => {
const newAnswers = [...answers];
newAnswers[index] = answer;
setAnswers(newAnswers);
};
// RENDER QUESTIONS
const questionElements = data.map((question, index) => {
// ANSWERS ARRAY AND SHUFFLE
let answers = [...question.incorrect_answers, question.correct_answer];
shuffle(answers);
return (
<Question
key={index}
index={index}
question={question.question}
answers={answers}
setAnswer={setAnswer}
/>
);
});
If I am not wrong I understand that you want to update parent component's state. You can pass setState function as props to child component like this:
return (
<Question key={index} index={index}
question={question.question}
answers={answers}
updateScore ={setScore}/>
)
You can make use of props for the same and pass setScore hook to set the values.
You can make use of Redux and get/set the values to/from it.
Most people prefer redux over passing the setState hooks to children as props, however many times people pass the hook as props and solve this.
Hope this helps you!

How can I limit the input tag to only 10 Commas?

You need an input custom that limits you to 10 commas.
When it is 10, it should be possible to modify it.
Currently, the input event works faster, so additional commas are added so it cannot be edited.
my code
// input props
value={relationSearch}
onChange={(e) => handleOnChangeInput(e)}
onKeyPress={handleKeyPress}
//callbackfunction
const relationCallbackFunction = {
handleOnChangeInput: (e) => {
setRelationSearch(e.target.value);
},
};
// input event area
const handleOnChangeInput = (e) => {
relationCallbackFunction.handleOnChangeInput(e);
};
const handleKeyPress = (e) => {
const currentStr = relationSearch.split(',');
console.log(e.target.value);
if (currentStr.length > 11) {
e.target.value ='';
}
};
you could do this a couple of ways, but here is a simple solution. The important part here is to ensure you set your input to a controlled input, where you provide the value from react.
We need a function which will implement your logic. It should take in a string and the total number of commas you want, and limit that string to the comma amount.
Below is a very simple function that does this. It splits the string using commas, ensures the result array stays at 10 length, and returns a joined string from the resultant array.
function ensureCommas(str, commas = 10) {
const commaArray = str.split(',');
const reduced = commaArray.slice(0, commas);
return reduced.join(',');
}
Now to use it. Here is a very simple App component which keeps the input value in state and provides this state value to the input, and has an onChange event handler which calls the above function on every key press
import { useState } from "react";
import "./styles.css";
function ensureCommas(str, commas = 10) {
const commaArray = str.split(",");
return commaArray.slice(0, 10);
}
export default function App() {
const [value, setValue] = useState("");
const onInputChange = (e) => {
const inputVal = e.target.value;
const newInputVal= ensurecommas(inputVal , 10);
setValue(newInputVal);
};
return (
<div className="App">
<input value={value} onChange={onInputChange}></input>
</div>
);
}
CodeSandbox
Remove the handleKeyPress function from the input props and update the relationCallbackFunction object.
const relationCallbackFunction = {
handleOnChangeInput: (e) => {
let value = e.target.value;
const currentStr = value.split(",");
if (currentStr.length <= 10) {
setRelationSearch(value);
}
}
};

How to change showing data?

I take datas from json file and there is so much data inside because of this I don't want to show all. In the page I want to show just 20 data and when I click NEXT button i want to see another 20 data. It's working but in the filter function it update filteredDatas but don't update showingDatas
import data from './data.json'
var i = 0
var j = 20
function App() {
const [showingDatas, setShowingDatas] = useState([])
const [filteredDatas, setFilteredDatas] = useState([])
const getDatas = () =>{
setFilteredDatas(data)
setShowingDatas(data.slice(0,20))
}
useEffect(()=>{
getDatas()
},[])
const next = () => {
i += 20;
j += 20;
setShowingDatas(filteredDatas.slice(i,j))
}
const filter = () => {
setFilteredDatas(data.filter(item => item.productCompany === "Mavi"))
i = 0
j = 20
setShowingDatas(filteredDatas.slice(0,20))
}
You can not do this like that, because of React working principles. But if you want to do that I suggest you use useEffect to keep filteredData changes, then in useEffect set it. Like:
useEffect(() => {
setShowingDatas(filteredDatas)
}, [filteredDatas]);
If you really want to do with useState I suggest you search useState with callback.

REACT Dynamic checkboxes

Purpose: I want to create any # of rows containing any # of checkboxes that will be handled by a useState hook.
Problem: Page becomes frozen / constant loading state with nothing showing. No console logs, and debugger doesn't even start. React usually will prevent endless loops of updates. But in this case it didn't get caught.
What I've tried:
console.logs (nothing gets outputted)
debugger statements (nothing
gets paused)
Cant do much bc of frozen page.
CODE:
const CreateCheckboxes = ({ rows, cols }) => {
const classes = useStyles()
const [checked, setChecked] = useState({})
const [isLoaded, setIsLoaded] = useState(false)
//temp initializer
let state = {};
//configures state based on unique checkbox name.
const handleChange = (e) => {
const value = {
...checked,
[e.target.name]: e.target.checked,
}
setChecked(value)
};
//Helper function
const createBoxes = (row, col) => {
let rowArr = [];
for (let i = 0; i < row; i++) {
let checkboxArr = []
for (let j = 0; i < col; j++) {
checkboxArr.push(
<Checkbox
name={`row-${i}-checkbox-${j}`} //unique identifier in the state.
checked={checked[`row-${i}-checkbox-${j}`]}
onChange={(e) => handleChange(e)}
/>
)
//store temp state so that react useState is given a list of initialized 'controlled' states.
//react deosnt like undefined states.
state[`row-${i}-checkbox-${j}`] = false;
}
rowArr.push(
<div className={classes.row}>
<Typography>{`Sound ${i}`}</Typography>
{/* JSX array */}
{checkboxArr}
</div>
)
}
// JSX array
return rowArr
}
//output as a jsx array of 'x row divs' contiaining 'y checkboxes'
const sequenceData = createBoxes(rows, cols)
useEffect(() => {
setChecked(state)
setIsLoaded(true)
}, [])
return isLoaded && (
<>
{sequenceData}
</>
);
}
Solution: Check your loop conditions. Inner loop set to i instead of j.
yes, but I think that component doesn't need to have state various.
I tried to create one. You can check it out following when you have time to see :)
https://codesandbox.io/s/stackoverflow-dynamic-checkboxes-5dh08
Solution: Check your loop conditions. Inner loop set to i instead of j.

Getting Blank Array on First Click?

I am stuck with a Reactjs problem. The problem is mentioned in App.js file. Please look into this. Initially all buttons are White and not clickable, how can I change there color after some time. Should I use useEffect hook or some other way. I am not getting the index of button randomly to change their color and isDisable.
Three Type of Button
White: initially all buttons are white and not clickable.
Red: Randomly from the white button, One button will be red and clickable.
Blue: Clicked Button. After clicking on the red button color will change to blue and not clickable.
When all white buttons become blue, give custom popup in the center of the screen with the icon "You won game…!!!"
I am trying below code to change button color randomly, but fails.
const changeColorHandle = () => {
const rand = Math.floor(Math.random() * 4);
btnArray.map((i, k) => {
if (i.index === rand) {
return ([...btnArray, { color: 'red', isDisable: false }])
}
return [...btnArray]
})
console.log(rand)
};
useEffect(() => {
if (btnArray.length > 0) {
setTimeout(() => {
changeColorHandle()
}, 2000);
}
}, [btnArray])
Can anybody please solve this problem Here. And show me where I am doing it wrong.
Thanks.
One thing you don't want to really mess with the new react hooks is that they are not really applying changes synchronously, that way sometimes you can have a little delay.
I don't think it's the issue here but you can have a more efficient code that also solve the problem :)
Code Sandbox is here
Raw snippet with lines changed is here:
import React, { useState } from "react";
import Button from "./Button";
const Game = () => {
const [buttonValue, setButtonValue] = useState(0);
const [btnArray, setBtnArray] = useState([]);
const handleChange = e => {
setButtonValue(parseInt(e.target.value, 10));
};
const onClickHandle = () => {
const tmpArray = [];
for (let i = 0; i < buttonValue; i++) {
tmpArray.push({ color: "white", isDisable: true });
}
setBtnArray(tmpArray);
};
const changeColorHandle = () => {
alert("clicked");
};
(rest is ok 👍)
So as you can see, buttonValue is removed, and the job of the onClick is just to generate a new array according to the actual value of the input.
You don't get any issue with the user playing with the input value as the btnArray will only be rendered once he has clicked the button :)
(rendering triggered by the state change done with setBtnArray()
Edit: One nice sweet addition could be getting rid of buttonValue as a string as you may use math operations on it rather than using string conversion back and forth
Essentially, you're setting multiple states very quickly in succession. By the time you get to your loop, your buttonCount state is behind and thus the loop where you're pushing values onto btnArray doesn't really run (ends immediately).
Quick aside: You're attempting to mutate btnArray directly inside the loop, don't do that if possible. Create a new Array, push values onto that and then setBtnArray to the new Array.
This works:
const onClickHandle = () => {
if (btnArray.length > 0) {
setBtnArray([]);
}
let newBtnArray = [];
for (let i = 0; i < parseInt(buttonValue, 10); i++) {
newBtnArray.push({ color: "blue", isDisable: true });
}
setBtnArray(newBtnArray); };
I threw out btnCount here, but if you need it for something else, you can set it seperately.
I have figured out your issue, you are setting the button count after clicking so it is running one step behind the array values, and button color change function is also added, check this: CodeSandBox: https://codesandbox.io/s/dreamy-ganguly-e805g?file=/src/components/Game.js
import React, { useState, useEffect } from "react";
import Button from "./Button";
const Game = () => {
const [buttonValue, setButtonValue] = useState("");
const [buttonCount, setButtonCount] = useState(0);
const [btnArray, setBtnArray] = useState([]);
const handleChange = e => {
//you are not using button value any where
//setButtonValue(e.target.value);
setButtonCount(parseInt(e.target.value, 10));
};
const onClickHandle = () => {
if (btnArray.length > 0) {
setBtnArray([]);
}
// setButtonCount(parseInt(buttonValue, 10)); //actually you were setting the count very late so it is one step behind
setButtonValue("");
console.log(buttonCount);
let tempArr = [];
for (let i = 0; i < buttonCount; i++) {
tempArr.push({ index: i, color: "white", isDisable: false });
setBtnArray(tempArr);
}
console.log(btnArray);
};
const changeColorHandle = btnIndex => {
console.log(btnIndex);
//change that button color and save btn array again
let newBtnArray = btnArray.map(btn => {
if (btn.index === btnIndex) {
btn["color"] = "blue";
} else {
//else change all buttons color back to white
btn["color"] = "white";
}
return btn;
});
setBtnArray(newBtnArray); // set new array with modified colors
};
return (
<>
<div className="container">
<div className="row">
<div className="col">
<input type="text" onChange={handleChange} />
<button onClick={onClickHandle}>Enter</button>
</div>
</div>
<br />
<div className="row">
{btnArray.map((i, k) => (
<Button
key={k}
color={i.color}
isDisable={i.isDisable}
onClick={() => changeColorHandle(i.index)}
/>
))}
</div>
</div>
</>
);
};
export default Game;

Categories

Resources