3

In jQuery, it's pretty trivial to perform an action, only once, when an event is triggered on the html element.

For example:

$(".dropdown-trigger").on("click", function(event) { var self = this; $("html").one("click", function() { $(self).parents(".dropdown").removeClass("active"); }); $(this).parents(".dropdown").toggleClass("active"); event.stopPropagation(); }); 

In React, it's not so easy.

How can I perform an action, in React, when the html element is clicked, and perform that action only once (similar to my jQuery code)?

7
  • Possible duplicate of React JS onClick event handler Commented Mar 3, 2016 at 21:15
  • @JCOC611 read the question again; OP is asking how to set up a handler which automatically unbinds after being fired once. Commented Mar 3, 2016 at 21:18
  • OP, you can do this with a state flag, but your jQuery example code suggests this is an XY problem. I would never use .one to do something like clear a dropdown. Commented Mar 3, 2016 at 21:19
  • @Mathletics Thanks. Can you give me an example? I'd be happy to reward you the answer. Commented Mar 3, 2016 at 21:29
  • 1
    Again, this is an XY Problem. Please tell us what problem you are trying to solve and what you would like to achieve. You are asking how to implement a particular (and IMO probably ineffective) solution. Commented Mar 3, 2016 at 22:06

5 Answers 5

8

With useState, also for React > 16.8

import React, { useState } from 'react'; const App = () => { const [clicked, setClicked] = useState(false); const myFunc = () => alert('hey'); const onClick = (event) => { if (!clicked) { setClicked(true); myFunc(event); } }; return <button onClick={onClick}>Click on me (once)</button>; }; 
Sign up to request clarification or add additional context in comments.

Comments

7

Using refs. Not very beatiful but at least it works. This requires React >16.8

import React, { useEffect, useRef } from 'react' const App = () => { const ref = useRef() // Wait for DOM to load useEffect(() => ref.current.addEventListener('click', () => alert('Hi'), { once: true }) return <button ref={ref}>Click on me (once)</button> } 

Comments

2

Flip the state once the element has triggered its event, react will re-render the component since your state changed and it won't be displayed. Below is an example of one way to achieve that outcome.

_onEvent() { this.setState({ hasTriggered: true }) } render() { return ( <div> { !this.state.hasTriggered ? <MyElement onClick={this._onEvent.bind(this)} /> : null } </div> ) } 

Comments

0

Functional programming's higher order method

Ref:

https://betterprogramming.pub/how-to-allow-only-one-click-events-in-javascript-72938027fbf5 

Gist:

const once = (f) => { let finished = false; return (...args) => { if (!finished) { finished = true; f(...args); } }; }; 

Complete example:

  • clicker 0 will fire events on every click.
    • this is the problematic one.
    • this can cause extra triggers to server-side if the network is slow and the user clicks again hoping the data will be submitted faster.
  • clicker 1 will only fire again, after the button itself and the click function ref is re-rendered.
    • this is a simple solution if we have to re-render the main component after the server has responded.
  • clicker 2 will only fire again when value2 has any changes.
    • if we want to control using specific state
import "./App.css"; import { useState, useCallback } from "react"; const once = (f) => { let finished = false; return (...args) => { if (!finished) { finished = true; f(...args); } }; }; function App() { const [value1, setValue1] = useState("value1"); const [value2, setValue2] = useState("value2"); console.log(`app rendered`); const onChange1 = useCallback((e) => { console.log(`onChange1`, e.target.value); setValue1(e.target.value); }, []); const onChange2 = useCallback((e) => { console.log(`onChange2`, e.target.value); setValue2(e.target.value); }, []); const onClick0 = () => { // mocking 2 secs network request setTimeout(() => { // set value to value1 to cause the re-render setValue1(new Date()); console.log("clicker 0"); }, 2000); }; const onClick1 = () => { // mocking 2 secs network request setTimeout(() => { // set value to value1 to cause the re-render setValue1(new Date()); console.log("clicker 1"); }, 2000); }; const onClick2 = () => { // mocking 2 secs network request setTimeout(() => { // set value to value1 to cause the re-render setValue1(new Date()); console.log("clicker 2"); }, 2000); }; const memoOnceOnClick2 = useCallback(once(onClick2), [value2]); return ( <div className="App"> <header className="App-header"> <input value={value1} onChange={onChange1} /> <input value={value2} onChange={onChange2} /> <button onClick={onClick0}> clicker 0 / run every time i am clicked </button> <button onClick={once(onClick1)}> clicker 1 / run once until i am re-render </button> <button onClick={memoOnceOnClick2}> clicker 2 / run once until value2 is changed </button> </header> </div> ); } export default App; 

Comments

0

This is a much simpler solution, in my opinion:

<div className={`tile ${tile.marked ? "team1" : ""}`} onClick={!tile.marked ? () => markTile(tile.id) : null} ></div> 

This is with React 18. As you can see, I'm checking if I've already clicked it by checking if the marked property is false. Only then do I call my function.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.