4

I am trying to understand the exact difference in terms of how the re-render of function component is caused in one case using plain setState V/s other case which uses functional state update

The relevant code snippet is as below

Case 1 : Causes re-render of the component

const onRemove = useCallback(
  tickerToRemove => {
    setWatchlist(watchlist.filter(ticker => ticker !== tickerToRemove));
  },
  [watchlist]
);

Case 2 : Does not cause re-render

const onRemove = useCallback(tickerToRemove => {
  setWatchlist(watchlist =>
    watchlist.filter(ticker => ticker !== tickerToRemove)
  );
}, []);

Full example of both the use-cases can be seen on;

https://codesandbox.io/s/06c-usecallback-final-no-rerenders-bsm64?file=/src/watchlistComponent.js

https://codesandbox.io/s/06b-usecallback-usememo-ngwev?file=/src/watchlistComponent.js:961-970

UPDATE

Full article link https://medium.com/@guptagaruda/react-hooks-understanding-component-re-renders-9708ddee9928#204b

I am a bit confused as to how the re-render of child components is prevented.

In the article it says

"Thankfully, setter function from useState hook supports a functional variant which comes to our rescue. Instead of calling setWatchlist with the updated watchlist array, we can instead send a function that gets the current state as an argument"

However, I am a bit confused whether the re-rendering of child components is prevented because we use empty array (as [] does not changes between renders) V/s prevented because of using setter variant of useState hook ?

copenndthagen
  • 49,230
  • 102
  • 290
  • 442

1 Answers1

1

Using a functional state update or not is rather irrelevant to the question you are asking about. You appear to be asking why (1) a callback with dependency triggers a rerender versus (2) a callback with empty dependency.

The answer is quite literally very simple. In version (2) you are providing a stable callback reference from the time the component mounts that never changes, whereas in (1) the callback reference changes when the dependency does. Remember that React components rerender when state or props update (a new callback reference is a new prop reference) or when the parent component rerenders. Since the onRemove prop is updating in (1) it triggers a rerender.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Oh k...Actually I was referring to this article which has this example (Under useCallback & useMemo section) https://medium.com/@guptagaruda/react-hooks-understanding-component-re-renders-9708ddee9928#204b – copenndthagen Oct 24 '21 at 08:50
  • In the article it says.....Thankfully, setter function from useState hook supports a functional variant which comes to our rescue. Instead of calling setWatchlist with the updated watchlist array, we can instead send a function that gets the current state as an argument. – copenndthagen Oct 24 '21 at 08:50
  • So I am a bit confused now....whether the re-rendering of all other child components is prevented because we use empty array ([] does not changes between renders) and not because of using setter variant of useState hook ? – copenndthagen Oct 24 '21 at 08:51
  • @copenndthagen The functional state update has more to do with correctly updating from the previous state (*wherever or however it was previously updated*) versus the "standard" update which uses whatever state value is closed over in callback scope. In version (1) it is necessary to add the state to the `useCallback` dependency array so the current value can be reenclosed in callback scope, i.e. avoid stale state enclosures. In version (2) using a functional state update the issue of stale enclosures is avoided entirely. – Drew Reese Oct 24 '21 at 08:55
  • I am not entirely clear when you say "reenclosed in callback scope" and "stale enclosures" ...trying to read it again to see if it can make sense....but in case, you can simplify that slightly (may be via example or something), that would be really helpful... – copenndthagen Oct 24 '21 at 08:58
  • @copenndthagen Updating state in the parent rerenders the parents, and subequently all children are rerendered. When the `onRemove` prop is stable ***and*** the React key remains, React can bail on rerendering the child. If the `onRemove` prop *also* changes (because it was redeclared in parent), then React will rerender the child. – Drew Reese Oct 24 '21 at 08:59
  • Thanks again....But what about "watchlist" state...Isn't that state getting updated when "onRemove" gets invoked and if this state gets updated (either via normal useState Or via setter variant of useState), shouldn't that cause automatic re-rendering of parent (WatchlistComponent) and child (TickerComponent) ? – copenndthagen Oct 24 '21 at 09:02
  • @copenndthagen Ah, yeah, if you use the `useCallback` hook with an empty dependency and reference any local component state, the value you will see is the state value from the render cycle the callback was created in. If you try to filter a state array it will always be the same initial state being referenced. This is the stale state I'm referring to. By adding state to the dependency array, you can reenclose the updated state value in the callback. – Drew Reese Oct 24 '21 at 09:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238477/discussion-between-drew-reese-and-copenndthagen). – Drew Reese Oct 24 '21 at 09:05