Too many re-renders useState React js - javascript

I want to display objects from the array one by one by clicking on the card.
To do this, I made a check for array length (props.tasks.length), and if it is more than 1, then the array is shuffled and then I want to display these objects one by one on click.
But after a click, a new array is generated each time and often the objects are repeated 2-4 times.
But I get an error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
function App(props) {
let [random, setRandom] = useState({});
let [newArr, setNewArr] = useState(props.tasks.sort(() => Math.random() - 0.5));
let i = 0;
random = newArr[i]
setRandom(newArr[i])
const randomCard =()=> {
console.log(random);
console.log(i);
console.log(newArr[0],newArr[1],newArr[2],newArr[3], newArr[4], newArr[5]);
console.log(newArr[i]);
if (i <= newArr.length) {
i++
} else {
newArr = props.tasks.sort(() => Math.random() - 0.5);
console.log('clean');
i=0
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{random.name}</p>
<p>{random.translate}</p>
<p>{random.note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;

The main culprit in your code example is
setRandom(newArr[i])
Using a hook's set method should only be done via an action or wrapped in a useEffect for side effects. In your code, setNewArr is called on every rerender and will cause the component to rerender... causing the infinite loop of rerenders.
You also don't need to store the selected element from your array in a state, you're essentially doing this by just storying the index in the use state.
Your solution should look something like this.
Also you want to reset i when it's < newArr and not <= newArr because if your array is of length "2" and i is "2" then you're setting i to "3" which doesn't point to any element in your list.
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
function App(props) {
// Default empty list
const [newArr, setNewArr] = useState([]);
// Sets the default index to 0
const [i, setI] = useState(0);
// We don't want to call shuffle on every rerender, so only setNewArr
// Once when the component is mounted.
useEffect(() => {
setNewArr(shuffle(props.tasks));
}, []);
const randomCard =()=> {
if (i < newArr.length) {
setI(i + 1);
} else {
setNewArr(shuffle(props.tasks));
setI(0);
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{newArr[i].name}</p>
<p>{newArr[i].translate}</p>
<p>{newArr[i].note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;

Related

React Hool re-render loop test Condition on state

I want to update and re-render for loop test condition on hook state update. After I update state hook the state is rerendered but I want to the loop to be rerendered too. I make a form. The length of the form depends on what number I enter on input.
Here is what I tried:
const [formLength, setFormLength] = useState(0);
let eachForm = () => {
for (let x = 0; x < formLength; x++) {
return <div>{x+1} inpute</div>;
}
};
useEffect(() => {
eachForm();
}, [formLength]);
{formLength <= 0 ? (<>Zeroo</>) : (<><eachForm /></>)}
There is no need for the useEffect hook here, it's not doing anything for you. You can render directly the JSX from the formLength state. eachForm also isn't a valid React component, so it can't be used like <eachForm />. The for-loop will also return during the first iteration and not return an array of div elements like I suspect you are expecting.
Example:
const [formLength, setFormLength] = useState(10);
return (
<>
{formLength
? Array.from({ length: formLength }).map((_, i) => (
<div key={i}>{i+1} input</div>
))
: <>Zero</>
</>
);
When the state changes your component is rerendered so there is no need for useEffect. Try something like this:
const [formFields, setFormFields] = useState([]);
return (
<div className='App'>
<input type='button' onClick={()=>
setFormFields([...formFields,<p key={formFields.length}>{formFields.length}</p>])}
value="+"/>
<form>
{formFields}
</form>
</div>
);

How can I update the props.data from a child component?

I am trying to display a range from an array that is being passed into a child.
My current Parent component is as follows:
import data from './data.json'
return (
<Cell symbol={data.symbol} number={data.number} />
)
And that data is being passed into my Child component:
const [updatedSymbols, setUpdatedSymbols] = useState()
useEffect(() =>
if(props.number === 1 || props.number === 2 || props.number === 3 ){
setUpdatedSymbols(props.number)
console.log("updated: " + props.number)
}
}, [props.symbol])
return (
<div class="cell">
{updatedSymbols}
</div>
)
QUESTION: If you look at the useEffect, you will notice that within the if-statement, I am selecting the numbers I want and simply passing them into the useState, "setUpdatedSymbols".
My problem is that there are too many numbers that I need to select, about 100, how can I push them all into updatedSymbols without using || ?
whatever the numbers list you want to check before you insert into your state you can collectively do this
const [updatedSymbols, setUpdatedSymbols] = useState()
const range = (from, to ) => {
var collection = [];
for(let i = from; i<=to ; i++) {
collection.push(i);
}
return collection;
}
useEffect(() =>
if(range(1,100).includes(props.number) ){
setUpdatedSymbols(props.number)
console.log("updated: " + props.number)
}
}, [props.symbol])
return (
<div class="cell">
{updatedSymbols}
</div>
)
// this is magical trick
// [1,2,3,4,5].includes(props.number); //if props.number happens to be in the array then it i'll give true

JS open tag with if and close it in another one

I have react component that gets index from map function, I'm trying to open div of row tag when my index is even and close it when my index is odd
render() {
return (
{this.props.index%2===0 && (<div className="row mt-1">)} //new row
<div className="col-1">{this.props.title}</div>
<div className="col-5">
<ProgressBar
variant={this.props.color}
now={this.props.now}
max={this.props.max}
label={this.props.label}
/>
</div>
{this.props.index%2===1 && (</div>)} //end of the row
);
}
this code in not compile:
enter image description here
the point is that every row contain two ProgressBar. What is the right way to do it?
You need to deal with whole elements are once, not tags.
This is easier if you break it up into functions.
You can use splice on an array to grab two items at a time.
e.g.
function createRow(elements) {
return (
<div>
{elements.map(createProgressBar)}
</div>
);
}
function createProgressBar(element) {
return (
<div>{element.index}</div>
);
}
function render() {
// ...
const rows = [];
while (myArray.length) {
const thisRow = myArray.splice(0,2);
rows.push(createRow(thisRow));
}
return rows;
}
You should modify the shape of your array into something like this before trying to render it.
[1,2,3,4] => [[1,2],[3,4]]
that way it would be easier for you to wrap it inside div.
see the live demo
Code to transform your flat array into nested array:
const list = [1, 2, 3, 4];
const [state, setState] = React.useState([]);
React.useEffect(() => {
let res = [];
for (let i = 0; i < list.length; i += 2) {
res.push([list[i], list[i + 1]]);
}
setState(res);
}, []);
Render logic:
{state.map(item => {
return (
<div style={{ border: "1px solid black", padding: "1em" }}>
{item.map(i => i)}
</div>
);
})}

React: Transitions are cut after element re-render further in the elements array

While creating a swap list in React, I encountered a strange transition bug. The way the list works is simple: you click on an element (A), its background transitions to a lighter color, then on another (B), they swap places, and element A, now on a new position, transitions back to the original color.
At least that's what's happens when element A has a higher index then element B. When it's the other way round, the transition after the swap is cut.
I managed to find a workaround using window.requestAnimationFrame, but it's not perfect. The state of the transition isn't preserved, which means that it transitions back always from the full light color. It doesn't matter that much here, but it's an issue in my other project. Sometimes the transitions are also cut anyway.
The code is simple. The thing to note is that elements preserve their keys after the swap. I created a code sandbox for you to play around.
import React, { useState } from "react";
const Item = props => {
let classes = "item";
if (props.selected) {
classes += " selected";
}
return (
<div className={classes} onClick={props.onClick}>
{props.value}
</div>
);
};
const App = () => {
const [list, setList] = useState([1, 2, 3]);
const [selected, setSelected] = useState(-1);
const select = index => {
setSelected(index);
};
const swap = index => {
const newList = [...list];
[newList[index], newList[selected]] = [newList[selected], newList[index]];
setList(newList);
setSelected(-1);
};
// The workaround that kind of works, but is not perfect.
// const swap = index => {
// const newList = [...list];
// [newList[index], newList[selected]] = [newList[selected], newList[index]];
// setList(newList);
// window.requestAnimationFrame(() => {
// setSelected(index);
// window.requestAnimationFrame(() => {
// setSelected(-1);
// });
// });
// };
const onClick = selected < 0 ? select : swap;
const items = list.map((value, index) => (
<Item
key={value}
value={value}
selected={selected === index}
onClick={onClick.bind(this, index)}
/>
));
return <div className="list">{items}</div>;
}
Here are the key css rules:
.item {
background: #0b7189; // darker
transition: background-color 1s;
}
.item.selected {
background-color: #228cdb; //lighter
}
I'm looking for a solution that is more reliable than my workaround.
All help will be appreciated! :)
I've made three changes in your code, so upon swapping the highlighted box remains the same one, then turns off:
Added the line setSelected(index); inside the swap function.
Added a setTimeout in order to delay the colour change.
Changed the key inside the map loop to the index value, as it must be unique and therefore it's just a better practice to use the index.
function App() {
const [list, setList] = useState([1, 2, 3]);
const [selected, setSelected] = useState(-1);
const select = index => {
setSelected(index);
};
const swap = index => {
const newList = [...list];
[newList[index], newList[selected]] = [newList[selected], newList[index]];
setList(newList);
// This was added in order to keep the highlight after swap.
// Note that it takes a second to take place
setSelected(index);
// And this was added in order to allow the colour to lighten,
// before returning to the original colour
setTimeout(() => setSelected(-1), 1000);
};
const onClick = selected < 0 ? select : swap;
const items = list.map((value, index) => (
<Item
key={index}
value={value}
selected={selected === index}
onClick={onClick.bind(this, index)}
/>
));
return <div className="list">{items}</div>;
}

Loop through array in react onclick

I have an array of objects. How can I loop through this array on click?
test = ['a', 'b', 'c', 'd']
{this.state.test.map(function(i){
return <span> {i} </span>
})}
I would normally loop through like this in react but this prints them all out at once. How can I display 'a' on the page and then the next time i click it, I display 'b' so on until the end of the array?
I need a function that can tell where I am in the array and display that on the page at the right moment
You could implement e.g. a counter variable and use Array#slice to show specified amount of elements inside the test array.
Codesandbox link
import React from "react";
export default class Hello extends React.Component {
state = {
test: ["a", "b", "c", "d"],
index: 0
};
handleClick = () => {
let i = this.state.index < this.state.test.length ? this.state.index += 1 : 0;
this.setState({ index: i });
};
render() {
return (
<div>
{this.state.test.slice(0, this.state.index).map(v => {
return (
<span>{v}</span>
);
})}
<button onClick={this.handleClick}>Click</button>
</div>
);
}
}
Edit: I was playing with it and actually I got even a better solution, which allows you to avoid re-looping Array#map on every render, with little help of hidden attribute. Also we are getting rid of the Array#slice function.
Improved codesandbox link
app.js
{this.state.test.map((v, i) => {
return (
<span hidden={i >= this.state.index}>{v}</span>
);
})}
And the Span.js component:
<span hidden={this.props.hidden}>
{this.props.v}
</span>
set initial index at your state first, then use that index as index array in span. next make some function to handle the increment, simply put it like this
const switchNinjaHandler = () => {
let looper = ++this.state.index % this.state.test.length;
setState({
...personsState, // WE SHOULD ALWAYS GIVE THE EXCACT INITIAL STATE VALUE, THIS IS WHERE SPREAD COMING HANDY
this.index : looper,
});
to display
return ( <div> {this.state.test[this.index]} </div> )

Categories

Resources