1

I want to create a custom hook in which I add a click event listener to a DOM element which calls a function defined in a React component which uses a state variable.

I add the event listener, but when I call the function, it does not reflect any state changes, it is always taking the initial state value.

const useCustomHook = (functionDefinedInComponent) => {
  // logic to get the dom element
  element.addEventListener('click', () => functionDefinedInComponent(item)); 
};

const CustomComponent = () => {
  const [state, setState] = useState(...);

  const customFunction = (item) => {
    setState(...); // change the 'state' variable
    // use item and state to do something
  }

  useCustomHook(customFunction);

  return ...;
}

When I click the DOM element to which I added the click event, the customFunction triggers with initial state value. Is there any to solve this?

11
  • Why do you want to do this? It seems a little clumsy when really all you need to do is add a click handler to the button in the JSX. Commented Nov 26, 2021 at 10:10
  • 1
    In that case you need to use event delegation. Just attach one listener to a parent element to catch the events from those buttons as they "bubble up" the DOM. Commented Nov 26, 2021 at 10:20
  • Where does element comes from inside your hook? And what is the difference between writing 50+ onClick and writing hook call 50+ times? Commented Nov 26, 2021 at 10:21
  • @Mr.Hedgehog I gather all the elements with querySelectorAll, traverse them and add listener to each in the hook. Commented Nov 26, 2021 at 10:27
  • So, you have N buttons and want to attach same listener to all of them? Commented Nov 26, 2021 at 10:29

2 Answers 2

1

I meant something like this. you might have to wrap your callback function in React.useCallback as well.

const useCustomHook = (functionDefinedInComponent) => {
  React.useEffect(() => {
     // logic to get the dom element
      element.addEventListener('click', () => functionDefinedInComponent()); 
  }, [functionDefinedInComponent])
 
};

Can you try this out and let me know what sort of problem you get.

Here is a code sandbox that you were trying to do. https://codesandbox.io/s/rakeshshrestha-nvgl1?file=/src/App.js

Explanation for the codesandbox example

Create a custom hook

const useCustomHook = (callback) => {
  React.useEffect(() => {
    // logic to get the dom element
    const el = document.querySelector(".clickable");
    el.addEventListener("click", callback);
    // we should remove the attached event listener on component unmount so that we dont have any memory leaks.
    return () => {
      el.removeEventListener("click", callback);
    };
  }, [callback]);
};

so, I created a custom hook named useCustomHook which accepts a function as a parameter named callback. Since, we want to attach an event on element with class clickable, we should wait till the element gets painted on the browser. For this purpose, we used useEffect which gets called after the component has been painted on the screen making .clickable available to select.

const [input, setInput] = React.useState("");

  const logger = React.useCallback(() => {
    alert(input);
  }, [input]);

  useCustomHook(logger);

  // render

Here, I have a state input which holds the state for the textbox. And also a function named logger which I passed to my custom hook. Notice, that the function logger has been wrapped inside of useCallback. You don't need to wrap it in this case, but it was there so that every time the component rerenders a new logger function won't be created except the changes in the input state.

Sign up to request clarification or add additional context in comments.

5 Comments

can you describe me what kind of thing you are trying to achieve? I can help you with proper code.
In the custom hook I want to get all the DOM elements with a common property, iterate over them and set their onClick to trigger a function passed as parameter to the custom hook. Later, in the component, I call the hook and pass a function defined in the component which is using some states defined also in the component. The problem is, when I'm calling the hook, it adds the function to the click event, but the states values inside the function remain the same they were when the click event listener was added. The function passed to the custom hook does not react to the state changes.
@Ghost sorry for the delay. I have updated my question with codesanbox link can you try it out?
This looks pretty good. Can you explain a bit what you did so I can accept it?
I've updated my answer with some explanation. Hope it helps.
0

You can use a public component like this:

const ClickableComponent = props => {
    const { title, handleClick, component: Component } = props;
    return (
        <Component onClick={handleClick}>{title}</button>
    )
};

export default ClickableComponent;

You can use this component like below:

<ClickableComponent title="your title" handleClick={handleClick} component={<button/> } />

2 Comments

This is not helpful for me. The "button" is not a classic button. I have some images positioned on a canvas and I want to make them clickable... it's a little bit more complicated. For a classic button this would work well.
You can also send component and use it instead of <button> in the JSC const { title, handleClick, Component } = props; return <Component onClick={handleClick}>{title}</button>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.