question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to prevent re-rendering of component when selectorFamily state remains unchanged?

See original GitHub issue

Hi,

Really love Recoil, and have started using it in a project quite extensively. I clearly see the potential of this state mgmt library. I have setup an app with multiple atoms and selectors, and it all works very smooth, however, I have one scenario that I have yet to solve in a clever manner, and I have not been able to find more info about it on your docs.

I have a table with multiple rows. Each row is a React.memo sub-component that uses Recoil hooks. The Recoil part of the component looks as follows (excuse my variable namings):

const setMarkInvalidModalState = useSetRecoilState(markInvalidModalState)
const setMarkInvalidSelectTrade = useSetRecoilState(markInvalidSelectTradeState)

const setOpenTradeModal = useSetRecoilState(openTradeModalState)
const setOpenTradeSelectTrade = useSetRecoilState(openTradeSelectTradeState)

const markForTrade = useRecoilValue(getMarkForTrade(compositeKey));

The only place i subscribe for updates and re-renderings is useRecoilValue of getMarkForTrade. getMarkForTrade is a selectorFamily, that looks as follows:

const getMarkForTrade = selectorFamily({
  key: 'MarkForTradeState',
  get: (compositeKey: string) => ({get}) => {
    const allMarks = get(getTradeMarks);

    const mark = allMarks[compositeKey];
    return ( mark !== undefined ? mark : null) as TradeMark | null;
  },
});

Now, whenever the underlying selector getTradeMarks (called in getMarkForTrade) is updated, all components subscribed to getMarkForTrade is also getting re-rendered. Even though the output / state of getMarkForTrade has remained unchanged for the input.

Basically all rows in the table are re-rendered every time, although 90% of the rows have the same output state from getMarkForTrade.

How can I prevent this? I have tested multiple times that this is the selector that triggers the re-render.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:7
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

6reactions
wuzzebcommented, Aug 16, 2020

I’ve been using this in the same situation, a table where only the row that was changed gets re-rendered.

export function useMemoizedRecoilValue<T>(recoilValue: RecoilValueReadOnly<T>): T {
  const [, forceRender] = React.useReducer((s: number) => s + 1, 0);
  const lastVal = React.useRef<T>();
  const lastRecoilValue = React.useRef<RecoilValueReadOnly<T>>();
  const init = useRecoilCallback(
    ({ snapshot }) => () => {
      lastVal.current = snapshot.getLoadable(recoilValue).contents as T;
      lastRecoilValue.current = recoilValue;
    },
    [recoilValue]
  );
  useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
    const cur = snapshot.getLoadable(recoilValue).contents as T;
    if (cur !== lastVal.current || lastRecoilValue.current !== recoilValue) {
      lastVal.current = cur;
      forceRender();
    }
  });

  if (lastRecoilValue.current !== recoilValue) {
    init();
  }

  return lastVal.current as T;
}

Doesn’t work with promises and just uses !== to compare, but both could be supported by simple changes to the code.

In my opinion, the best built-in solution would be that the selector is passed the old value to the get function and the selector can implement any checks or whatnot itself. You can see in #314 that it is hard to implement a generic method of suppressing updates so my idea is:

  • selector is passed the old value
  • if the selector returns the exact same javascript object passed to old, downstream updates are suppressed. This is just like the recent change to atoms which suppressed updates with the exact same object is set.

This way selectors that don’t care just ignore the old input and the few selectors which need to suppress downstream updates can implement checks at the beginning of the function.

0reactions
salvoravidacommented, Jan 12, 2021
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to prevent re-rendering of components that have not ...
IOW: keep state as close to the component as possible, ... previous props with the new props, and if they're the same, it...
Read more >
5 Ways to Avoid React Component Re-Renderings
These Hooks reduce re-renderings by caching and returning the same result if the inputs are the same without any computations. When the inputs...
Read more >
Asynchronous Data Queries - Recoil
Recoil provides a way to map state and derived state to React components via a ... from a selector get callback, the interface...
Read more >
How to stop re-rendering lists in React? - Alex Sidorenko
Components always re-render. First, let's simplify our example by removing all props from the Item . We will still update the parent state...
Read more >
Prevent Excess Rerenders in React | by Maciek Wątroba
This is going to prevent react from re-rendering a component if its props ... As long as the values in the array remain...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found