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

Elegant.Scripting
  • 757
  • 4
  • 10
  • 28
  • Possible duplicate of [React JS onClick event handler](http://stackoverflow.com/questions/28511207/react-js-onclick-event-handler) – JCOC611 Mar 03 '16 at 21:15
  • @JCOC611 read the question again; OP is asking how to set up a handler which automatically unbinds after being fired once. – Evan Davis Mar 03 '16 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. – Evan Davis Mar 03 '16 at 21:19
  • @Mathletics Thanks. Can you give me an example? I'd be happy to reward you the answer. – Elegant.Scripting Mar 03 '16 at 21:29
  • What happens after that event? Is the element still visible, but not clickable or hidden? – Jack Mar 03 '16 at 21:55
  • @Jack the active class makes another child element visible and clickable, that originally was not visible or clickable. – Elegant.Scripting Mar 03 '16 at 21:56
  • 1
    Again, this is [an XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-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. – Evan Davis Mar 03 '16 at 22:06

5 Answers5

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>;
};
laurens
  • 81
  • 1
  • 2
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.addEventListener('click', () => alert('Hi'), { once: true })

  return <button ref={ref}>Click on me (once)</button>
}
Paul Losev
  • 969
  • 1
  • 12
  • 17
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>
    )
}
Jack
  • 8,851
  • 3
  • 21
  • 26
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;
Aung
  • 191
  • 1
  • 4
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.

LAGS
  • 46
  • 3