How to change button text in ReactJS - javascript

How to change the button text when someone click on?
Code:
<Button disabled={this.state.disabled}
type="primary"
htmlType="submit"
style={{
background: '#ff9700',
fontWeight: 'bold',
border: 'none',
float: 'center',
}}
loading={this.state.loading}
onClick={this.enterLoading}
value="Next"
id="buttontext"
onClick="changeText()"
>
Next
</Button>

Mayank is correct.
Create a variable called "text" (or whatever you choose) and put that instead of "Next".
state = {
text: "Next"
}
changeText = (text) => {
this.setState({ text });
}
render() {
const { text } = this.state //destucture state
return (
<Button
onClick={ () => { this.changeText("newtext")} }> {text} </Button> )...etc
Note: this method will always change the text to "newtext" when you click. You can pass a variable there as well to make it more dynamic.
Hope this helps.
Update: Just saw Mayank comment. That code is essentially what I have. Just a tip you no longer need a constructor and you don't have to bind your methods anymore.
Updated: React Hooks
Same thing but with the useState hook. Instead of calling the state variable text, I am using buttonText to be more explicit. The updated version would look like:
import { useState } from 'React';
const [buttonText, setButtonText] = useState("Next"); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
const changeText = (text) => setButtonText(text);
return (
<Button onClick={() => changeText("newText")}>{buttonText}</Button>
)
You can omit the changeText function all together and have this:
return (
<Button onClick={() => setButtonText("newText")}>{buttonText}</Button>
)
Updated: How to Add Set Timeout
Adding an update to answer a question in the comments: "If I wanted to use a setTimout to bring the button back to the previous text after 1 second where would I add that in?"
There are two ways that comes to mind: add the setTimeout to the changeText function or create an effect that depends on the buttonText.
change text
You can just pop the setTimeout right in this function.
Goes from this
const changeText = (text) => setButtonText(text);
to this
const initialState = "Next";
const [buttonText, setButtonText] = useState(initialState); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
const changeText = (text) => {
setButtonText(text);
setTimeout(() => setButtonText(initialState), [1000])
}
We add the initialState variable as a const to keep track of the "previous text". Since, it should never change we could define it in all caps snake case like const INITIAL_STATE meh your choice.
useEffect
We still need to define that initialState variable, again so we can keep track of the original. Then we can create a useEffect which is a React hook that allows you to "hook" into changes of a variable (that's only a part of useEffect, just enough to get us going here).
We can break the effect down into two essential parts: the body or callback of the effect, what do we want to do when the effect runs and the dependency or what triggers the effect to run. In this case, our callback will be setTimeout and set the button text inside that timeout and our buttonText will trigger the effect.
Here's the effect:
useEffect(() => {
if(buttonText !== initialState){
setTimeout(() => setButtonText(initialState), [1000])
}
}, [buttonText])
Anytime the state variable buttonText changes this effect will run.
We start at
buttonText = initialState // "Next"
the effect runs and checks the if. Since buttonText equals the initialState the conditions evaluates to false and we terminate the callback and the effect.
When the user clicks the button, changeText executes and sets the buttonText state which updates the variable triggering the effect. Now we run that if check again and this time it passes so we execute the setTimeout.
Inside the timeout we are setting state so the effect runs again and this time it fails because we just changed the state back to initialState.
I recommend throwing a debugger in there or some logs to follow the trail
Long winded explanation. Here's what the whole component would look like using the effect approach.
import React, { useState, useEffect } from "react";
const FancyButton = () => {
const initialState = "Next";
const [buttonText, setButtonText] = useState("Next"); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
// the effect
useEffect(() => {
if(buttonText !== initialState){
setTimeout(() => setButtonText(initialState), [1000])
}
}, [buttonText])
const changeText = (text) => setButtonText(text);
return (
<button type="button" onClick={() => changeText("newText")}>{buttonText}</button>
)
};
I added the type on the button because that's a good practice. And changed "Button" to "button". You can certainly have any component there you want, this works better for copying and pasting

Where you wrote "Next", the button text, do this instead:
{this.state.disabled ? 'Disabled...' : 'Next'}
I this will display "Disabled..." when the state.disabled == true, and 'Next' when state.disabled == false.

Related

Why is the value on display not in sync with value logged to console?

Can anyone tell me why at button click, the value outputted to the console is always one unit smaller than displayed on the screen?
The values are not in sync as expected.
Example below in React
In Child:
import React, {useState } from "react";
export const ChildComp = ({getNumProps}) => {
const [num, setNum] = useState(0);
const onPlusClick = () => {
if (num< 12) {
setNum(num + 1);// num does not increase immediately after this line, except when focus reenters here on second method call
}
getNumProps(num);
}
return(
<div>
<button onClick={onPlusClick}>
Click to increment
</button>
{num}
</div>
);
}
In parent
import { ChildComp } from "./ChildComp"
export const ParentComp = () => {
const getNum= (num) => {
console.log(num);
}
return (<ChildComp getNumProps={getNum}/>)
}
The page initially shows 0
When I click once the number increments to 1, but console displays 0
When I click once the number increments to 2, but console displays 1
I should see in the console the same as the page display
Appreciate if you can leave a commen on how the question can be improved.
This is a child to parent communication example. Also, any objections about standards used, please let me know.
Thanks.
Update: I notice the values would be in sync if
instead of getNumProps(num);
I did getNumProps(num + 1); but that doesn't change the fact that previously on this line
setNum(num + 1);, as already pointed out in the comment, num does not increase immediately after this line, except when focus reenters here on second method call. Not sure why.
The prop function getNumProps is a side effect, and should be put into a hook, instead of inside of that onPlusClick function.
Instead, do this:
useEffect(() => {
getNumProp(num);
}, [num]);
Alternatively, to avoid the error: "React Hook useEffect has a missing dependency: 'getNumProps'. See this doc on using the useCallback hook
const callback = useCallback(() => {
getNumProp(num);
}, [num]);
function onPlusClick(...) {
...
callback();
}
The change to the state of num will cause a re-render of the child component, not the parent.

React state updating but rendering late

I've tried almost every solution similar to my problem, yet none is working. I have a simple state and changing the value of this state in a function as like below, handleOnClick is calling in a button's onClick event. I'm also using Router(if it's change something);
import { useState} from "react"
import { BrowserRouter as Router, Route, Link, useHistory} from "react-router-dom";
const Buton = () => {
let x = "";
const [lowerState, setLower] = useState("")
const history = useHistory();
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
setLower(x)
console.log(x) //this prints the current value
console.log(lowerState) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerState})
};
.
.
.
return (...)
}
export default Buton
Now x is a value that returns from an input HTML element. When I set this value as a state and console log, it doesn't print the value first, when I put something in input again, then it prints the first value. So it's like it's coming 1 step behind.
I've used useEffect() , I did put a second parameter to setLower as console.log(lowerState) and other things on the internet that people suggested, but none is working. Every time, the state is coming 1 step behind. How can I make this state changes immediately?
If you want to use the value of an input in a user event function, the best way (and least buggy) is to bind your input value to local state and then just reference that state in your callback function.
Please try to avoid imperatively pulling values from the DOM using getElementById etc. Here's what I mean:
const [value, setValue] = useState('');
// This will keep everything updated until you need to use it
handleChange(event) {
setValue(event.target.value);
}
// Then just grab whatever is in local state
handleClick() {
history.push('/test', {params : value});
}
return (
<input value={value} onChange={handleChange} />
// Your button is here too
)
This is because when you call setLower(x) it is not an async call. So it doesn't wait. That's why you get the 1 step before value in your state right after setting the value.
Official doc - https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
When you call setLower(x), it doesn't immediately update the lowerState. The new value will be available the next time it renders. Because of that the console.log(x) "works" (since it uses the new value that you gain as a parameter) but console.log(lowerState) uses the old value that hasn't updated to the state yet at that point.
If you want history.push('/test', {params : lowerState}) to work, then you need to use the x there instead of lowerState. Or call it within a useEffect with the lowerState and having lowerState as a dependency parameter to the hook.
This is expected behaviour since React is updating state in a batch
Which mean that the state only gets an update after an eventHandler/function is finished
If you want to do some condition, wrap your logic inside a useEffect
useEffect(() => {
if (lowerState === "your-condition-value") {
history.push("/test", { params: lowerState });
}
}, [lowerState]);
Or in your case, just use the variable directly:
const handleOnClick = () => {
x = document.getElementById("my_input").value.toLowerCase();
history.push("/test", { params: x });
};
You should not worry about that since your app still working as expected
So i would like to suggest that use useRef if need for reference only object which may not causing rerendering. also using let x= "" is not correct, you should write code immutable way
const Buton = () => {
const lowerCaseRef = useRef("")
const history = useHistory();
const handleOnClick = () => {
lowerCaseRef.current =
document.querySelector("#my_input").value.toLowerCase();
console.log(lowerCaseRef.current) //this DOES NOT prints the current value, but
// when I put another text into the input and click
// to button, it prints the first value I put here
history.push('/test', {params : lowerCaseRef.current})
};
return (...)
}

React.js | Infinite render if I pass setState as a callback function, even after destructuring props

Issue
I have a child component that gets some button id-name configs as props, renders selectable HTML buttons according to those configs and returns the selected button's value(id) to the callback function under a useEffect hook. However it causes an infinite render loop because I need to pass the props as a dependency array. Note that React.js wants me to destructure props, but it still causes an infinite render loop even if I do that.
Child Component
import React, {createRef, useState, useEffect} from "react";
const OptionButton = ({ buttons, buttonClass, callback }) => {
const [value, setValue] = useState()
const refArray = []
const buttonWidth = ((100 - (Object.keys(buttons).length - 1)) - ((100 - (Object.keys(buttons).length - 1)) % Object.keys(buttons).length)) / Object.keys(buttons).length
useEffect(() => {
if (callback) {
callback(value);
}
}, [value, callback])
const select = (event) => {
event.target.style.backgroundColor = "#10CB81"
refArray.forEach((currentRef) => {
if (currentRef.current.id !== event.target.id) {
currentRef.current.style.backgroundColor = "#F5475D"
}
})
setValue(event.target.id)
}
return(
<span style={{display: "flex", justifyContent: "space-between"}}>
{Object.entries(buttons).map((keyvalue) => {
const newRef = createRef()
refArray.push(newRef)
return <button ref={newRef} id={keyvalue[0]} key={keyvalue[0]} className={buttonClass} onClick={select} style={{width: `${buttonWidth}%`}}>{keyvalue[1]}</button>
})}
</span>
)
}
export default OptionButton
So as you can see here my child component gets button configs as key-value (button value-button name) pairs, renders these buttons and when user clicks one of these buttons it gets the id of that button, sets it to 'value' constant using useState hook and then passes that value to parent component callback.
Parent Component
return(
<OptionButton buttons={{"ButtonValue": "ButtonName", "Button2Value": "Button2Name"}} callback={(value) => this.setState({buttonState: value})} buttonClass="buttonclass"/>
)
It's just fine if I don't use this.setState at the callback function. For example if I just do
(value) => console.log(value)
there is no infinite loop. As far as I can tell it only happens if I try to use setState.
What am I doing wrong? How can I fix this? Thanks in advance.
The reason why there is infinite loop is because you have callback as dependency in useEffect. And what you are passing to the component is you pass a new callback function on each new render, hence it always enters useEffect because of that. Since you are using classes consider passing instance method as callback instead of arrow function as here.
Also I think you are overusing refs. You could also achieve what you are doing by just storing say the id of clicked button, and then dynamically styling all buttons, e.g. inside map if id of current button is same as the one stored, use #10CB81 bg color in style object, otherwise different one.
Also there are better ways to check which btn was clicked, see here.

Click handler, in react, doesn't return latest change in state when it executes a 2nd time

This takes place in a functional component:
import {useEffect} from 'react';
let [clickedOnPiece, setClickedOnPiece] = useState(false);
let [testRender, setTestRender] = useState(false);
useEffect(() => {
testRenderFunction();
}, [])
function testRenderFunction() {
let el = <div onClick={onClickHandler}>Click me</div>;
setTestRender(el);
}
function onClickHandler() {
if (clickedOnPiece) {
console.log("already clicked")
return
}
console.log(clickedOnPiece); //returns false the 1st & 2nd time.
setClickedOnPiece("clicked");
}
return (
<>
{testRender}
</>
)
When I click on div for the first time, I wait until setClickedOnPiece("clicked") successfully updates clickedOnPiece to "clicked". (I check this with React Developer Tools).
When I click div the 2nd time, it doesn't log the new change in state. It still logs clickedOnPiece as false. Why is this?
Okey this problem is because useState is asyncronus. u can read more about this useState set method not reflecting change immediately.
I think the solution is add useEffect like this.
useEffect( () => {
console.log(clickOnPiece);
}
, [clickOnPiece])
If you want to toggle the state, you could do something like this:
let [clickedOnPiece, setClickedOnPiece] = useState(false);
const onClickHandler = () => {
// set the value of clickedOnPiece to the opposite of what it was
// i.e. if it was 'true', set it to 'false'.
setClickedOnPiece(!clickedOnPiece);
console.log(clickedOnPiece);
}
// call the onClickHandler on click
<div onClick={()=>onClickHandler()}>Click me</div>
Looks like you are toggling
let [clickedOnPiece, setClickedOnPiece] = useState(false);
const onClickHandler = () => {
console.log(clickedOnPiece);
setClickedOnPiece(!clickedOnPiece);
}
console.log(clickedOnPiece);
<div onClick={onClickHandler}>Click me</div>
After setting state, don't console immediately because state is an asynchronous.
onClickHandler references the old, previous variable, clickedOnPiece. I believe this is because onClickHandler is not defined in the return statement part of the functional component which would have allowed it a new onClickHandler body to be created each time. Instead, we have the old onClickHandler continually referencing the old clickedOnPiece.
This problem is known as 'stale closures' - a concept I found discussed well at the bottom of this article

React setState in If Statement

I'm making a chess game using React and I'm trying to write a function that computes the squares that should be highlighted when a certain chess piece is selected. I have the game as one component and each chess piece as a second component, and the button in the chess component calls a function from the game component when clicked.
I want to highlight the appropriate squares if and only if the selected piece is the correct color, so I had a setState call in an if statement in my handleClick() function. After reading Issue with setState() in if statement I moved the conditional so that the handleClick function is only linked to pieces of the correct color.
The issue now is that the state gets changed as desired, but for some reason the components don't rerender to reflect that change. Can someone please let me know how I could fix this? Here's my code for handling clicks:
handleClick(num){
this.setState(prevState => {
return {
gameBoard: prevState.gameBoard,
turn: prevState.turn,
selected: num
}
})
}
and here's my code for creating the board:
<div>
{
this.state.gameBoard.map((object)=>
<div className = "board-row"> {object.map((object2) =>
<Piece key={object2.at} turn = {this.state.turn} selected = {object2.selected} piece = {object2.piece} identifier = {object2.at} onClick = {() => this.handleClick(object2.at)} color = {(object2.at+Math.floor(object2.at/8))%2 === 0?"white":"black"} />)}
</div>
)
}
</div>
try:
handleClick(num){
this.setState(prevState => {
return {
...prevState,
selected: num
};
})
}
Ths issue here is mostly a question of dependencies. Your rendering is based on the gameboard field of the state, which isn't being modified. The component therefore doesn't rerender since it doesn't know that there are additional dependencies (selected) for it.
The easiest and cleanest approach I would suggest is to simply move your map function inside a useCallback (or useMemo, both should work) with the proper dependencies, then simply use it in your rendering method.
const foobar = useCallback( () => this.state.gameBoard.map((object)=>
<div className = "board-row"> {object.map((object2) =>
<Piece key={object2.at} turn = {this.state.turn} selected = {object2.selected} piece = {object2.piece} identifier = {object2.at} onClick = {() => this.handleClick(object2.at)} color = {(object2.at+Math.floor(object2.at/8))%2 === 0?"white":"black"} />)}
</div>
)
, [this.state] )
render (
<div>
{foobar()}
</div>
)

Categories