Reputation: 97
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>
}
Upvotes: 4
Views: 11478
Reputation: 24181
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>
Upvotes: 0
Reputation: 112787
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>
Upvotes: 9