React Hool re-render loop test Condition on state - javascript

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>
);

Related

Too many re-renders useState React js

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;

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

React useState, setState and {state} in return

I come across the rendering issue with React State.
The problem is that {state} in return get value one beat late.
But the console log in handleChange shows right value.
If the previous value of state is 9, current value of state value is 10 then the console.log({state}) in handleChange shows 10 and the <span>{state}<span> in return shows 9.
It looks different from other state async problem.
I can't understand why this happened.
const [findText, setFindText] = useState("");
const [findCount, setFindCount] = useState(0);
const handleChange = (e) => {
let str = e.target.value;
setFindText(str);
let cnt = 0;
doxDocument.map((docx) => {
cnt += docx.src.split(findText).length - 1;
});
setFindCount(cnt);
console.log({findCount})
};
return(
<div>
<input
type="text"
value={findText}
onChange={handleChange}
/>
<span>{findCount} found <span>
</div>
);
Two problems...
findText will not have been updated to the new value when you use it in split(). Either use str instead or calculate findCount in a memo or effect hook with a dependency on findText.
You're completely misusing filter(). Use reduce() to calculate a computed sum
const [findText, setFindText] = useState("");
const findCount = useMemo(
() =>
findText
? doxDocument.reduce(
(sum, { src }) => sum + src.split(findText).length - 1,
0
)
: 0,
[findText, doxDocument] // may not need doxDocument
);
return (
<div id="App">
<input
type="text"
value={findText}
onChange={(e) => setFindText(e.target.value)}
/>
<span>{findCount} found</span>
</div>
);

How to toggle two different buttons (function/text) based on external condition (e.g. num of items purchase)

I am revisiting an earlier idea to toggle between two buttons conditionally in a CRA.
import ...
const ...
export const Index = () => {
// Boolean to toggle buttons conditionally
const [reachMax] = React.useState( id <= 8 );
return (
<div>{(reachMax &&
<div{(reachMax &&
<button
id="btn1"
type="button"
className="..."
onClick={event}
>
Event A: "Sales!"
</button>
</div>)
||
<div>
<button
id="btn2"
type="button"
className=" "
disabled='true'
>
Event B: "Out of Stock"
</button>
</div>)
}
)
}
Using a state hook. The condition for the Boolean ( contract.tokenUri.id <= 8 ) is taken from an external smart contract dynamically. How can the condition be set so that it would not return an undefined error?
You're trying to access this in an arrow function. Besides not knowing what this will point to, in React that's not how you access props in functional components.
I assume you use Index like this in your Toggle component:
<Index data={...} />
Then you access it like:
export const Index = (props) => {
const data = props.data;
// ....
};
Managed to resolve by setting this condition in order to read the ID off the smart contract:
const reachMax = mintedState.state === "SUCCESS" && mintedState.data.length <= 8;
There's no need for the constructor as the state is directly passed into the app from the external contract. Once the nmintedState.state condition is satisfied (success), the data.length (a proxy for the ID) can be used as the final condition for setting the right button.
This problem is specific to the combination of Solidity contract and ReactJS used.

Why counter defined with useRef hook is not incrementing on each render?

I am trying use the hook useRef() to keep a counter of which items to display next, and increment the counter on each render and using the counter value to slice in elements in an array. On each render the counter index.current should increase by 10 but in my case its not increasing and I keep getting the same 10 elements over and over. I can't figure out why. The collection overview component here is mapping over the collection preview component 4 times.
//CollectionPreview
const CollectionPreview = ({title,movieItems,Counter}) => {
const index = React.useRef(0)
const movieData = movieItems.slice(index.current, index.current + 10)
index.current += 10
return (
<div className="collection-preview">
<h1 className="title">{title.toUpperCase()}</h1>
<div className="preview">
{console.log(movieData)}
</div>
</div>
);
}
const mapStateToProps = state => ({
movieItems: selectMovieItems(state),
})
export default connect(mapStateToProps)(CollectionPreview);
//CollectionOverview
class CollectionOverview extends React.Component {
render() {
return (
<div className="collection-overview">
{
CollectionData.map(items =>
<CollectionPreview key={items.id} title={items.title} />)
}
</div>
);
}
}
export default CollectionOverview;
Personally I think counting the number of renders is bad pattern, as renders can be called multiple times in different occasions and its difficult to keep track of every single render call as your app gets bigger.
I think a better approach is to have a initialIndex or pageIndex in some state somewhere. As you are using Redux, I think you should put that in the reducer and create the appropriate actions to increment this value.

Categories

Resources