4

I don't understand why I'm getting infinite loop in useClick I see that I change state value inside useEffect using setVal but useEffect should work only on onClick as specified in second param. I thought that it is because the param onClick i pass is memoized but the callback is not called(i checked that using console.log('go set')

function useClick(onClick, setVal, val) {
  React.useEffect(() => {
    console.log('Click');
    setVal(val + 1);
  }, [onClick]);
}

const Home = () => {
  const [val, setVal] = React.useState(0);
  const incrementOnClick = React.useCallback(() => {
    console.log('go set');
    setVal(val + 1);
  } , [setVal, val]);
  useClick(incrementOnClick, setVal, val);
  return <div>
    <div>{val}</div>
    <button onClick={incrementOnClick}>Click me</button>
 </div>
}
3
  • 2
    I think the loop it's due to the call of useClick inside Home Component: Home calls useClick, inside useClick you change the val property of Home, thus the Home Component` it's re-rendered, thus the function Home it's called again, thus useClick is executed again.. And there is the loop Commented Mar 6, 2019 at 12:47
  • 1
    Looking at your code I'm not sure what your trying to do. Remember that every time Home gets rendered your going to get another instance of incrementOnClick, so passing that as the last parameter to useEffect is not going to do anything. Commented Mar 6, 2019 at 12:49
  • Yes, that's why I'm using the useCallback function to avoid Commented Mar 6, 2019 at 12:59

2 Answers 2

9

val and setVal will change on every render, which in turn will cause incrementOnClick to become a new function reference, and your useClick effect will always be invoked.

You could instead give a function as first argument to setVal. This function gets the current val as argument and returns the new value. This way incrementOnClick will always be the same function.

const { useEffect, useState, useCallback } = React;

function useClick(onClick, setVal, val) {
  useEffect(() => {
    console.log("Click");
    setVal(val + 1);
  }, [onClick]);
}

const Home = () => {
  const [val, setVal] = useState(0);
  const incrementOnClick = useCallback(() => {
    console.log("go set");
    setVal(val => val + 1);
  }, []);

  useClick(incrementOnClick, setVal, val);

  return (
    <div>
      <div>{val}</div>
      <button onClick={incrementOnClick}>Click me</button>
    </div>
  );
};

ReactDOM.render(<Home />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

The code above shows how you could get away from the infinite loop and could be valuable for experimentation, but most of it isn't necessary. You could write the same functionality like this instead:

const { useState } = React;

const Home = () => {
  const [val, setVal] = useState(1);
  
  return (
    <div>
      <div>{val}</div>
      <button onClick={() => setVal(val + 1)}>Click me</button>
    </div>
  );
};

ReactDOM.render(<Home />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

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

Comments

0

Looking at what your trying to do, I believe your missing one of the most useful features of Hooks & React, and that's composition.

Here is an example of what you have done, but just creating another component called <IncrementButton/>, to me it just makes the code much easier to understand / debug. Custom hooks are great, but for doing this I believe it's the wrong tool..

const { useEffect, useState } = React;

const IncrementButton = props => {
  const {val, setVal, children} = props;
  return <button
    onClick={() => setVal(val + 1)}
  >{children}</button>;
}

const Home = () => {
  const [val, setVal] = useState(0);
  return (
    <div>
      <div>{val}</div>
      <IncrementButton val={val} setVal={setVal}>
        Click me
      </IncrementButton>
    </div>
  );
};

ReactDOM.render(<Home />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

1 Comment

Sure, this question was mostly to ask if I'm understanding hooks. Of course the final button would look separated like you wrote. Thanks

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.