最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React: Perform action, only once, when html element is clicked - Stack Overflow

programmeradmin1浏览0评论

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

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

Share Improve this question edited Mar 3, 2016 at 21:28 Elegant.Scripting asked Mar 3, 2016 at 21:11 Elegant.ScriptingElegant.Scripting 8374 gold badges11 silver badges29 bronze badges 7
  • Possible duplicate of React JS onClick event handler – JCOC611 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. – Evan Davis 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. – Evan Davis Commented Mar 3, 2016 at 21:19
  • @Mathletics Thanks. Can you give me an example? I'd be happy to reward you the answer. – Elegant.Scripting 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. – Evan Davis Commented Mar 3, 2016 at 22:06
 |  Show 2 more comments

5 Answers 5

Reset to default 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>;
};

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>
}

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

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;

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.

发布评论

评论列表(0)

  1. 暂无评论