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.

[Feature request] Customizable control over when a selector triggers re-render

See original GitHub issue

Proposal

I would like to be able to specify a custom function for cache comparison on an atom/selector so I can tweak the behaviour. Let’s say we have a configurable algorithm with multiple steps, and some options that pertain to a step each.

const algorithmOptions = atom({
	key: 'algorithmOptions',
	default: {
		featureA: true,
		featureB: true,
		featureC: false,
	},
});

Now, only step 2 options are relevant for the step 2 of the algorithm, so we make a selector to fetch only that

const step2Options = selector({
	key: 'step2Options',
	get: ({get}) => {
		const allOptions = get(algorithmOptions);

		return {
			allOptions.featureB,
			allOptions.featureC,
		};
	},
	// overwrite cache comparison, so we don't trigger unnecessary rerenders by modifying algorithmOptions.featureA
	cacheCompare: (old, new) => old.featureB=== new.featureB && old.featureC === new.featureC,
})

That way, we don’t trigger an unnecessary rerender when doing setOptions({ ...options, featureA: !options.featureA }).

Motivation

This can currently be done using an intermediate selector, but it’s a lot of boiler plate and makes the code look clunky. Here’s a code sandbox showing the intermediate selector pattern: https://codesandbox.io/s/busy-cherry-u4rx6?file=/src/App.tsx

The real life use cases for this are when you get a state object from a backend, which you want to split out into multiple parts, or if you have a form for configuration of something, and you want to save the state of the form as is.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:11
  • Comments:31 (6 by maintainers)

github_iconTop GitHub Comments

7reactions
rpggiocommented, Feb 18, 2022

I am experimenting with a couple of extensions that might serve as a workaround. Interested to know if there are any obvious problems. Demo

/**
 * Use a writable selector to prevent excess renders.
 * If the setting value is equal to the current value, don't change anything.
 */
export function equalAtom<T>(options: EqualAtomOptions<T>): RecoilState<T> {
  const { key, equals, ...innerOptions } = options;
  const inner = atom({
    key: `${key}_inner`,
    ...innerOptions
  });

  return selector({
    key,
    get: ({ get }) => get(inner),
    set: ({ get, set }, newValue) => {
      const current = get(inner);
      if (!equals(newValue as T, current)) {
        set(inner, newValue);
      }
    }
  });
}
/**
 * Use a wrapper selector to prevent excess renders.
 * If the latest selection is value-equal to prior ref, return the prior ref.
 */
export function equalSelector<T>(
  options: EqualSelectorOptions<T>
): RecoilValueReadOnly<T> {
  const inner = selector({
    key: `${options.key}_inner`,
    get: options.get
  });

  let prior: T | undefined;

  return selector({
    key: options.key,
    get: ({ get }) => {
      const latest = get(inner);
      if (prior != null && options.equals(latest, prior)) {
        return prior;
      }
      prior = latest;
      return latest as T;
    }
  });
}
6reactions
drarmstrcommented, Dec 2, 2021

As was mentioned, this can currently be addressed via intermediate selectors from #314. Though, it makes sense to improve the API for this without intermediate selectors.

API options to consider:

  • custom comparator for a selector to compare it’s output value to determine if it should propagate updates downstream.
  • custom comparator for a selector to compare dependencies to determine if it should re-evaluate
  • custom comparator provided at individual callsites, such as from hooks or selector get() to determine if it should re-render or re-evaluate.
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to prevent re-rendering of components that have not ...
Personally I would avoid React.memo / React.useRef / React.useCallback . The simplest solution to your example is just create another ...
Read more >
Just Say No to Excessive Re-Rendering in React - GrapeCity
In this article, we will address instances of excessive re-rendering in React applications and demonstrate how to avoid them.
Read more >
React re-renders guide: everything, all at once - Developer way
The guide explains what are re-renders, what is necessary and unnecessary re-render, what can trigger a React component re-render.
Read more >
7 Rerendering Partial Page Content - Oracle Help Center
In some cases, you may want a component to be rerendered only when a particular event is fired, not for every event associated...
Read more >
ASP.NET Core Razor component rendering | Microsoft Learn
Control the rendering flow ... In most cases, ComponentBase conventions result in the correct subset of component rerenders after an event occurs.
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 Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found