Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving memoized selectors outside of the component? #65

Open
TomiS opened this issue Aug 24, 2020 · 2 comments
Open

Moving memoized selectors outside of the component? #65

TomiS opened this issue Aug 24, 2020 · 2 comments

Comments

@TomiS
Copy link

TomiS commented Aug 24, 2020

Hey, I'm writing this mostly to ask feedback and possibly suggest a new example on the web page.

I've been slightly bothered by the fact that without something like the reselect library (for redux) it's quite easy to end up causing unwanted rerenders with reductive. (In real life - at least in my reality - the global state often needs to be manipulated into another shape before using it inside the components)

The current example on the page shows one should useCallback hook to avoid rerenders but as we know useCallback cannot live outside of a React component. So if we blindly follow the example from the current docs (below), we end up writing most of our complicated selectors inside React components that likely ends up causing some unnecessary code duplication if multiple components need to use the same selectors.

// Current example from the docs
[@react.component]
let make = (~id) => {
  let productSelector =
    React.useCallback1(
      state => state.products->Belt.List.keep(p => p.id === id),
      [|id|],
    );
  let product = AppStore.useSelector(productSelector);
  ...
};

So after a bit of experimenting, I figured that while useCallback needs to be inside the component, the callback itself could be located outside of the component, so let's try to do that with some currying magic. We end up with this:

// The reusable selector factory, could be arbitrary complex in real world
let makeProductSelector = (~id, state) => state.products->Belt.List.keep(p => p.id === id);

// React component, that now contains a minimal useCallback
[@react.component]
let make = (~id) => {
  let productSelector =
    React.useCallback1(
      makeProductSelector(~id),
      [|id|],
    );
  let product = AppStore.useSelector(productSelector);
  ...
};

So my questions are:

  • Are there any performance/other problems with this approach I'm overlooking?
  • If no, could this be abstracted even further? (I'll keep thinking about this too, but I'll also put this "challenge" here in case someone immediately figures it out :) )
@froatsnook
Copy link

I solve this problem in a very similar way:

let useSelector = (f) => {
  let selector = React.useCallback0(f);
  AppStore.Store.useSelector(selector);
};

let useSelector1 = (f, deps: 'a) => {
  let selector = React.useCallback1(f, [|deps|]);
  AppStore.Store.useSelector(selector);
};

let makeSelectData = (~id, state) => {
  ...
};

[@react.component]
let make = (~id) => {
  let data = useSelector1(makeSelectData(~id), id);
  ...
};

@TomiS
Copy link
Author

TomiS commented Oct 12, 2020

Ah, of course... It's possible to wrap the selector into a reusable hook. Nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants