I am looking at the code in formik that apparently is a way around the stale closure problem with react hooks.
function useEventCallback
The documentation actually states:
In either case, we don’t recommend this pattern and only show it here for completeness. Instead, it is preferable to avoid passing callbacks deep down.
Let's say we can't avoid passing callbacks then the simplest way would be to use a callback for the state setter: setSomeState(currentState=>....return something based on current state)
I'm not sure how this would behave when concurrent mode is released but here is an example of how you can use a callback to the state setter:
const ParentContainer = () => {
//list is created and maintained in parent
const [list, setList] = React.useState([
{ id: 1, val: true },
{ id: 2, val: true },
]);
//simplest way to get current list is to pass a callback
// to the state setter, now we can use useCallback without
// dependencies and never re create toggle during this life cycle
const toggle = React.useCallback(
id =>
setList(list =>
list.map(item =>
item.id === id
? { ...item, val: !item.val }
: item
)
),
[]
);
return Parent({ list, toggle });
};
const Parent = ({ list, toggle }) => (
<div>
{list.map(item => (
<ItemContainer
key={item.id}
item={item}
//every item gets the same toggle function
// reference to toggle never changes during Parent life cycle
toggle={toggle}
/>
))}
</div>
);
//Added memo to make ItemContainer a pure component
// as long as item or toggle never changes the (render) function
// will not be executed
// normally a pure component should not have side effects so don't
// do side effects in pure compnents (like mutating rendered var)
// it is only to visibly display how many times this function was
// called
const ItemContainer = React.memo(function ItemContainer({
item,
toggle: parentToggle,
}) {
const rendered = React.useRef(0);
//toggling item with id 1 will not increase render for
// other items (in this case item with id 2)
// this is because this is a pure component and this code
// will not be executed due to the fact that toggle or item
// never changed for item 2 when item 1 changed
rendered.current++;
const toggle = React.useCallback(
() => parentToggle(item.id),
[item.id, parentToggle]
);
return Item({ toggle, item, rendered });
});
const Item = ({ toggle, item, rendered }) => (
<div
onClick={() => toggle(item.id)}
style={{ cursor: 'pointer' }}
>
<div>{item.val ? '[X]' : '[-]'}</div>
<div>times rendered:{rendered.current}</div>
</div>
);
//render app
ReactDOM.render(
<ParentContainer />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>