I am trying to prevent a button being clicked consecutively in my project and only allow a single click. I would also like it to only be a single click and not allow a double click if that is possible?
To do this I would like to add a time out of maybe 5 seconds before the button can be pressed again but I'm not sure how to do this. The button is a link to redirect the user back to the homepage.
Is the a way to set the button on a timer when clicked?
<Button id="back-btn" variant="link" className="btn btn-link" onClick={props.goBack} alt="homepage">
Homepage
</Button>
Any ideas?
Cheers
R
basically you need to use a disabled state with a timer.
check this codepen: https://codepen.io/hasanagh/pen/MWaLxVK
state = {
disabled: false,
};
handleButtonClicked = () => {
//going back logic
this.setState({
disabled: true,
});
setTimeout(() => {
this.setState(() => ({
disabled: false,
}));
}, 5000);
};
render() {
const { disabled } = this.state;
return (
<button
onClick={this.handleButtonClicked}
disabled={disabled}
>
Button to be disabled
</button>
);
}
Also, not sure why you need it to be 5 sec if this is related to a certain event better bind to event than time.
It's probably most re-useable to make your button component. You could handle the onClick event to set a disabled state, then start a timer to set it back to false. Example:
const DebouncedButton = ({ as = button, delay, onClick, ...props }) => {
const [isDisabled, setDisabled] = useState(false);
useEffect(() => {
if (!isDisabled) {
// timeout elapsed, nothing to do
return;
}
// isDisabled was changed to true, set back to false after `delay`
const handle = setTimeout(() => {
setDisabled(false);
}, delay);
return () => clearTimeout(handle);
}, [isDisabled, delay]);
const handleClick = (e) => {
if (isDisabled) {
return;
}
setDisabled(true);
return onClick(e);
};
const Component = as;
return <Component {...props} disabled={isDisabled} onClick={handleClick} />;
};
You would use this component just like you'd use a button, except that you pass it a delay which is the amount of time in milliseconds it should be disabled after clicking. The as prop lets you pass the component which is used for the button itself, defaulting to <button>.
<DebouncedButton
as={Button}
delay={5000}
id="back-btn"
variant="link"
className="btn btn-link"
onClick={() => console.log('click!')}
alt="homepage"
/>
Currently it sets the disabled property of the button to true, but if you don't want the visual, just remove disabled={isDisabled} from the component.
Related
React 18 changed useEffect timing at it broke my code, that looks like this:
const ContextualMenu = ({ isDisabled }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleMenu = useCallback(
() => {
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);
useEffect(() => {
if (isExpanded) {
window.document.addEventListener('click', toggleMenu, false);
}
return () => {
window.document.removeEventListener('click', toggleMenu, false);
};
}, [isExpanded]);
return (
<div>
<div class="button" onClick={toggleMenu}>
<Icon name="options" />
</div>
{isExpanded && <ListMenu />}
</div>
);
};
The problem is, toggleMenu function is executed twice on button click - first one is correct, it's onClick button action, which changes state, but this state change executes useEffect (which adds event listener on click) and this click is executed on the same click, that triggered state change.
So, what should be correct and most "in reactjs spirit" way to fix this?
Your problem is named Event bubbling
You can use stopPropagation to fix that
const toggleMenu = useCallback(
(event) => {
event.stopPropagation();
if (isDisabled) return;
setIsExpanded((prevState) => !prevState);
},
[isDisabled],
);
I have a button "Add to Cart" and I would like it to do two things when clicked. I want it to add an item to the cart and I also want it to Change the text to "added" for 1 second.
The problem is if I call onClick twice the second function overrides the first.
If I put both click handlers into 1 function and then call that in 1 single onClick the only the function adding things to the cart works.
Where am I going wrong?
const [variant, setVariant] = useState({ ...initialVariant })
const [quantity, setQuantity] = useState(1)
const {
addVariantToCart,
store: { client, adding },
} = useContext(StoreContext)
const handleAddToCart = () => {
addVariantToCart(productVariant.shopifyId, quantity)
}
const text = "Add To Cart";
const [buttonText, setButtonText] = useState(text);
useEffect(() => {
const timer = setTimeout(() => {
setButtonText(text);
}, 1000);
return () => clearTimeout(timer);
}, [buttonText])
const handleClick = () => {
setButtonText("Added");
handleAddToCart();
}
return (
<>
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
Add to Cart
</button>
{!available && <p>This Product is out of Stock!</p>}
</>
you need to use the buttonText inside the button as below, however, in your code you have used the hard text Add to Cart.
<button
className="add"
type="submit"
disabled={!available || adding}
onClick={handleClick}
>
{buttonText}
</button>
I have a Button that stays disabled according to mapped state variables. It works when the page is loaded, but after processing, the state changes but the Button stays enabled.
The state should transition like this
loading: false -> button disabled: true
when button is clicked:
loading: true -> button disabled: true
when processing finishes:
loading: false -> button disabled: false
The loading state is changed, however the disabled attribute just changes for the first time.
Page.jsx (just some snippets for simplicity)
const [disabled, setDisabled] = useState(true);
const { loading } = useSelector(state => state.spreadsheet);
const importData = () => {
importOperations.createRows(rows, dispatch);
};
return (
<>
<Button
variant="contained"
color="primary"
onClick={importData}
className={classes.spacing}
disabled={disabled || loading}
>
Import
</Button>
</>
);
importOperations.js
export const createRows = async (rows, dispatch) => {
dispatch(importActions.setLoading(true));
// ......
dispatch(importActions.setLoading(false)); // this step is correctly executed
};
importReducer.js
export const INITIAL_STATE = {
messagesLog: [],
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case spreadsheetActions.SET_LOADING:
return { ...state, loading: action.payload };
default:
return state;
}
};
Do you have any suggestion on why the button doesn't change back to disabled?
I would have commented and asked you to clarify the following first, but it won't let me comment, so I have to ask you here:
I think there may be a problem in the logic that you mentioned. But I could be wrong, so I am commenting here first before trying to answer your question.
You said you want the following but that won't work because, if loading is false, button disabled is true. If the button is disabled, you can't click on it.
You said when the button is clicked, you want loading to be true. That is fine, but you want button disabled to be false?? Would you want people to click on the button when it is loading?
loading: false -> button disabled: true
when button is clicked:
loading: true -> button disabled: false
when processing finishes:
loading: false -> button disabled: true
Regardless, I have put together some helpful code below to match what you asked. I'll be happy to assist further once you verify if the logic you mentioned is correct.
Can you try the following. it is the best I could put together to mimic your code somewhat
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [disabledState, setDisabledState] = useState(false);
const [loading, setLoading] = useState(false);
// sets loading to true when clicked, and
const importData = () => {
setLoading((loading) => !loading);
setTimeout(() => {
setLoading((loading) => !loading);
}, 1000);
};
// set loading to false initially when component mounts
useEffect(()=> {
setLoading(true)
},[])
// I would set disabled to true when loading else false
// but I have matched it to watch you mentioned in your post below
// you may changed your it accordingly to your needs here
useEffect(() => {
loading ? setDisabledState(false) : setDisabledState(true);
}, [loading]);
return (
<>
<span>{`${loading}`}</span>
<button
variant="contained"
color="primary"
onClick={() => importData()}
disabled={disabledState}
>
Import
</button>
</>
);
}
Here is a link to the CodeSandbox for the above: https://codesandbox.io/s/twilight-hill-urv8o?file=/src/App.js:0-1055
I am currently trying to build a rock-paper-scissor and what I intend to achieve are this logic:
after the start button clicked, a player has 3seconds to pick a weapon, if not, a random weapon will be picked for the player.
The problem:
When I picked a weapon under the 3seconds, it works just fine. But, when I intentionally let the setTimeout triggered, it is not updating the state automatically. I suspected the if conditions are not met, but I don't know why that happen.
Here is the code snippet:
//custom hooks//
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const weapons= ['rock', 'weapon', 'scissors']
const App = () => {
const [p1Weapon, setp1Weapon] = useState("");
const prevWeapon = usePrevious(p1Weapon);
const getPlayerTimeout = (playerRef, setPlayer, time) => {
setTimeout(() => {
if (playerRef === "") {
setPlayer(weapons[Math.floor(Math.random() * weapons.length)]);
}
}, time);
};
const startGame = () => {
getPlayerTimeout(prevWeapon, setp1Weapon, 3000);
}
return (
...
<div>
<button
className="weaponBtn"
onClick={() => {
setp1Weapon("rock");
}}
>
rock
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("paper")}>
paper
</button>
<button className="weaponBtn" onClick={() => setp1Weapon("scissors")}>
scissor
</button>
<button type="button" onClick={startGame}>
Start!
</button>
</div>
)
Thanks!
if all you want to do is set a state after x time you can do this very easily like this
this.setState({isLoading: true},
()=> {window.setTimeout(()=>{this.setState({isLoading: false})}, 8000)}
);
this should set the value of isLoading to false after 8 seconds.
I hope it helps
I have a series of buttons that execute internal logic(no forms not dependant on input), but call the functions asynchronously. I would like to disable the button after one click, and have tried several things on onclick() method but keep getting errors.
Code looks something like this:
{ this.state.isEthTransferVisible && <button id="button"
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
this is the function called from within the onclick
async handleTransferFromEthereum(){
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}
Add another state variable, such as this.isEthTransferEnabled (Default true). Change your button to:
{ this.state.isEthTransferVisible && <button id="button"
disabled={this.state.isEthTransferEnabled}
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
And change your handleTransferFromEthereum method:
async handleTransferFromEthereum(){
this.setState({ isEthTransferEnabled: false });
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isEthTransferEnabled: true,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}
Wrong syntax? It should be:
onClick={() => {
parseAddress(this.state.sc);
this.handleTransferFromEthereum();
}}