React Error: Too many re-renders. while using arrow function - javascript

I am making a calculator in react in which i made buttons for numbers and when button "7" is pressed then in the input field 7 is added.
My approach:
I am using useState to do this.
I made an arrow function funinpval which takes takes number as string in argument then i am using this function with different buttons onclick handler by passing respective numbers as arguments. But I am getting error
import React from 'react'
import { useState } from 'react';
export const Calculator = () => {
const [inpval, setInpval] = useState("")
const funinpval = (num) => {
setInpval(inpval + num)
}
return(
<>
<input type="text" value={inpval}>
<button onClick={funinpval("7")}>7</button>
<button onClick={funinpval("8")}>8</button>
</>
)
Can anyone please help

<button onClick={funinpval("7")}>7</button>
<button onClick={funinpval("8")}>8</button>
You are not waiting the user to click the buttons to execute the functions, they are instead executed every render phase, directly. Which mean that the component render -> state update -> new re-render -> new state update -> ...
To fix it:
<button onClick={() => funinpval("7")}>7</button>
<button onClick={() => funinpval("8")}>8</button>

There is a syntax error in how you are providing the event handlers.
You have to provide event handlers sonething like:
<button onClick={() => funinpval("7")}>7</button>
<button onClick={() => funinpval("8")}>8</button>
Simply writing onClick={funinpval("7")} will immediately call the function while rendering which sets the state. When state got updated then the component re-renders. Then again while re-rendering, this function got called and so on.

onClick={funinpval("7")}
will return the result of calling that function to the listener rather than a reference to the function that the listener can call. So you're setting state immediately with those two buttons which is causing the render which is calling the function again which is setting the state again... infinity!
In this example I pick up the textContent of the button and use that to set the new input state, and then you can simply just pass the reference to the function to the handler and let the function deal with how state is set.
const { useState, useEffect } = React;
function Calulator() {
const [inpval, setInpval] = useState(0);
function funinpval(e) {
// Grab the `textContent` of the button and
// relabel it to `num` making sure to coerce the
// text to a number first
const { textContent: num } = e.target;
setInpval(inpval + Number(num));
}
return(
<div>
<input type="text" value={inpval} />
<button onClick={funinpval}>7</button>
<button onClick={funinpval}>8</button>
</div>
)
};
// Render it
ReactDOM.render(
<Calulator />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Related

How can I make my HTML update when I press the update button?

function setTodoInfo(id) {
let todoInfo = todoInfoRef.current.value
if(todoInfo === "") return
todo.info = todoInfo
todoInfoRef.current.value = null
}
<>
<h1 className="delete-button"> hi, {todo.info} </h1>
<form>
<input type="text" ref={todoInfoRef}/>
</form>
<button onClick={closeInfo} className="delete-button" > Close </button>
<button onClick={setTodoInfo}> Set Info</button>
</>
When I click the set Info button its updating the info property on the todo, but it doesn't display it when you click, you have to close it and reopen it to see the updated info
react uses reference to be able to see it should or not rerender. using refs means the reference doesnt change...
as pointed, you should really see the docs on how to make it the "react way"
if you really need to make with references, then you could add some "render" function.
put in a useState a integer or something else, then call setState to change its value... that should force a render.
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update the state to force
}
(inside functional component)
const forceUpdate = useForceUpdate();
call forceUpdate where needed

React add html attribute to external component

I am using a component that I cannot change directly, but I would like to extend.
import { Button } from '#external-library'
// Currently how the button component is being used
<Button click={() => doSomething()} />
// I would like to add a tabIndex to the button
<Button click={() => doSomething()} tabIndex={0} />
I cannot add an attribute because the component is not expecting a tabIndex. I cannot directly modify the Button component.
How can I extend the <Button /> component so I can add attributes like tabIndex, etc?
I was hoping something like the following would work:
export default class ExtendedButton extends Button { }
// except I'm dealing with functional components
You can't edit custom component implementation without changing its internals.
// You can't add tabIndex to internal button without changing its implementation
const Button = () => <button>Click</button>;
In such cases, you implement a wrapper with desired props:
const Component = () => {
return (
<div tabIndex={0}>
<Button />
</div>
);
};
If the component forwarding ref (also depends to which element it forwarded in the implementation), you can use its attributes:
// Assumption that Button component forwards ref
const Button = React.forwardRef((props,ref) => <button ref={ref}>Click</button>);
<Button ref={myRef}/>
// Usage
myRef.current.tabIndex = 0;
You can access the inner DOM button element using React refs(read here)
most likely the external-lib you use provide a ref prop for the Button component which you use to pass your own create ref
const buttonRef = useRef(null);
<Button ref={buttonRef}/>
Then you can use buttonRef.current to add tabIndex when your data is ready to be populated in like
useEffect( () => {
if(buttonRef && buttonRef.current){
buttonRef.current.tabIndex = 2;
}
}, [props.someProperty] );

How to change text in a <p> tag when hovering a button in ReactJs?

I'm trying to change the text of a <p> to David and Larry accordingly when each button (that has an image inside) has hovered. I have experimented with numerous things and found a way to change the CSS of the button with a function. But I was unable to find anything to change text since <p> is in a different class. Any suggestions to address this problem?
For your information, I have added a CSS color changing function I used earlier to the below code sample.
here's my code.
import React from 'react';
import "./Tri.css";
function Tri() {
function davidon(e) {
e.target.style.background = 'red';
}
function davidoff(e) {
e.target.style.background = 'green';
}
function larryon(e) {
e.target.style.background = 'red';
}
function larryoff(e) {
e.target.style.background = 'green';
}
return (
<div>
<div>
<div>
<button onMouseOver={davidon} onMouseLeave={davidoff}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button onMouseOver={larryon} onMouseLeave={larryoff}>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>Larry Or David?</p>
</div>
</div>
</div>
);
}
export default Tri;
Thanks in advance for you replies.
You need to think more in "React", and use component state and props. The offical documentation is a good place to start.
Here I've got two components.
1) Tri: which has it's own state, and builds the HTML using Button components
2) Button: since you need each button to change color depending on the mouse action it's best to separate that functionality out into a new component so that each instance can have its own state.
(I've intentionally left out the images in this example, but you could pass in a src prop to the button and have that handle the images too if you wanted.)
const { useState } = React;
// `Button` accepts a props object
// Here I've destructured out the button name,
// and the handleHover function
function Button({ name, handleHover }) {
// We initialise the state with "green"
const [ color, setColor ] = useState('green');
function handleColor() {
// We set the new color based on the current color
setColor(color => color === 'red' ? 'green' : 'red');
// And then call the `handleHover` function, passing in `name`
handleHover(name);
}
return (
<button
className={color}
onMouseOver={handleColor}
onMouseLeave={handleColor}
>
{name}
</button>
);
}
function Tri() {
// In `Tri` we set its own state for the name
// initialised to an empty string
const [ name, setName ] = useState('');
// A handler that changes the name
// This is the function we pass to each button
function handleHover(name) {
setName(name);
}
// Set up two buttons using our Button component
// assigning a name to each, and passing in our handler
// Whenever the name (state) is changed the name in the
// paragraph also changes
return (
<div>
<div>
<Button name="Larry" handleHover={handleHover} />
<Button name="David" handleHover={handleHover} />
</div>
<p>{name}</p>
</div>
);
}
ReactDOM.render(
<Tri />,
document.getElementById('react')
);
.red { background-color: red; }
.green { background-color: green; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try using states. And don't change DOM-nodes dynamically in event handlers. Always use React functionality:
React uses a declarative form of programming (The Virtual DOM specifically). You define variables and set them and React updates the DOM if those change.
useState gives you the opportunity to declare an editable (through a setter function) variable. See Docs on State and Props.
import React from 'react';
import "./Tri.css";
function Tri(props) {
// props as immutable arguments (if needed)
// useState keeps an internal state in the component
let initialTxt = 'Larry Or David?';
const [text, setText] = React.useState(initialTxt);
return (
<div>
<div>
<div>
<button
className="david-btn"
onMouseOver={() => setText('David')}
onMouseLeave={() => setText(initialTxt)}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button
className="larry-btn"
onMouseOver={() => setText('Larry')}
onMouseLeave={() => setText(initialTxt)}>>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>{text}</p>
</div>
</div>
</div>
);
}
Also, extend ./Tri.css with the following code. You could use a style-variable but that would make your code more bloated and unreadable if you have access to CSS.
.david-btn,
.larry-btn {
background-color: green;
}
.david-btn:hover,
.larry-btn:hover {
background-color: red;
}
You are looking for Refs. You can read more about them in documentation.
I've created a simple example (based on your code).
Step by step what I did:
import useRef hook which is used to create reference.
import React, { useRef } from "react";
created reference:
const pTagRef = useRef();
passed reference to your p tag
<div ref={pTagRef} className="plex">
<p>Larry Or David?</p>
</div>
created function which can change the content of this reference where pTagRef.current is DOM element.
function setName(name) {
if (pTagRef.current) {
pTagRef.current.innerText = name;
}
}
called the function whenever name changed
setName("larry");
You should definitely use state for this but I hope this one helps you to get started.

How to ensure state is not stale in React hook

The buttons i create using below seems to lag in the selectedButtonIdx value.
Is the toggleSelected not complete by the time getClass is called ?
function ButtonGroup(props) {
const [selectedButtonIdx,setIdx]=useState(props.loadCurrentAsIndex);
const toggleSelected = (e) => {
setIdx(parseInt(e.target.dataset.index));
props.onclick(e);
};
const getClass = (index) => {
return (selectedButtonIdx === index) ? classnames('current', props.btnClass)
: classnames(props.btnClass)
};
let buttons = props.buttons.map((b, idx) => <Button key={idx} value={b.value} index={idx} text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}/>);
return (
<div>
{buttons}
</div>
);
}
Every onclick is expected to show the user which button in the group was clicked by changing its class.
By looking at this,
<Button
key={idx}
value={b.value}
index={idx}
text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}
/>
Button is your custom component,
Two things to notice here,
You have provided onclick (c is small) props, in you actual component it should be onClick={props.onclick}
You have used e.target.dataset.index, to work with dataset we should have attribute with data- prefix. So your index should be data-index in your actual component.
So finally your Button component should be,
const Button = (props) => {
return <button text={props.text} data-index={props.index} onClick={props.onclick} className={props.btnClass}>{props.value}</button>
}
Demo
The function setIdx, returned from useState is asynchronous, this means that it may be not be finished by the time you run your next function (as you guessed).
Take a look at useEffect it allows you to specify a function to run once an item in your state changes, this method will ensure your functions are called in the right order.
By now I don't see anything wrong here.
How it works:
initial render happens, onClick event listener is bound
user clicks a button, event handler calls setIdx triggering new render
new render is initiated, brand new selectedButtonIdx is used for rendering(and for getClass call as well)
See, there is no reason to worry about if setIdx is sync function or async.

Strange behavior of React setState with let variable change

The code below will render a <h1> and two <button>.
In my expectation, the changeString1 <button> will change letString to 1 and change <h1> text to 1 finally, while the changeString2 <button> will
change <h1> text to 3 if I click the changeString2 first
change <h1> text to 1 if I click the changeString1 first
But in fact
If I click the changeString1 once first, then click the changeString2, <h1> text will be 3 ! But why?
What's more, if I click the changeString1 twice first, then click the changeString2, <h1> text will be 1 ! But why?
It seems that both of 2 facts are contradictory...
You can test this by https://codesandbox.io/s/wy4l1y4o8
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
let letString = "3";
const [statString, setStatString] = useState("2");
function changeString1() {
letString = "1";
setStatString(letString);
}
function changeString2() {
console.log(letString);
setStatString(letString);
}
return (
<div>
<h1>{statString}</h1>
<button onClick={changeString1}>changeString1</button>
<button onClick={changeString2}>changeString2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Please look at the flow that is taking place:-
1st click on changeString1
state changes
component re renders
letString gets re-initialized to 3 because of let letString = "3"
value of letString = 3;
value of statString = 1 (state);
2nd click on changeString1
value of letString gets set to 1 in the function call;
But value of statString(state) is already 1 so no state changes and component never re-renders and letString is not re-initialized
After 2nd click values are:-
letString = 1;
statString = 1(state);
Now, when you click on changeString2 value of letString is 1 and statString is also 1 so state doesn't changes and nothing happens and you just see 1.
You should track clicking on changeString1 in a separate variable:
const {useState} = React
function App() {
const [statString, setStatString] = useState("2");
const [clicked, setClicked] = useState(false)
function changeString1() {
setClicked(true)
setStatString("1");
}
function changeString2() {
setStatString(clicked ? "1" : "3");
}
return (
<div>
<h1>{statString}</h1>
<button onClick={changeString1}>changeString1</button>
<button onClick={changeString2}>changeString2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root" />
The reason your code didn't work as expected is that each invocation of App creates a new instance of the letState variable that is bound to changeString2 function when it is defined.
React optimizes calls that don't change the state, avoiding unnecessary re-renders. So when you click the second time you are not causing a replacements of the callback functions. So when you click on changeState2 it's the same function defined in "previous" render.
When you click on changeString2, local variable letString value is 3.
If you want to persist letString value between render cycles you must use useState or even using a global scope variable (not recommended)

Categories

Resources