Reselect: Handling asynchronous select

Created on 27 Jun 2016  路  9Comments  路  Source: reduxjs/reselect

My store has 2 points and I want to calculate their distance through the google maps api.

I have been suggested there to use reselect since the distance is a resulting data from the store state.

I am ready to go. But now I wonder how to handle asynchronous calculation with reselect:

export const calculateItineraries = createArraySelector(
  state => state.point1,
  state => state.point2,
  (point1, point2) => {
    return new google.maps.DirectionsService.route({
    origin: {lat: point1.lat, lon: point1.lon},
    destination: {lat: point2.lat, lon: point2.lon},
    travelMode: point1.travelMode
  }, (response, status) => {
    if (status === google.maps.DirectionsStatus.OK) {
      return response;
    } else {
      return null;
    }
  });
  }
)

But since the route calculation is asynchronous, I am wondering how to deal with it.

Should my method return a promise that will wait for resolution? The docs doesn't mention anything about it.

Most helpful comment

I had a similar use case, and I ended up having to copy the main reselect selector code and alter it for promises.

function memoizeLastPromise(func) {
    let lastArgs = null;
    let lastResult = null;
    return (...args) => {
        if (
            lastArgs !== null &&
            lastArgs.length === args.length &&
            args.every((value, index) => value === lastArgs[index])
        ) {
            return lastResult;
        }
        return func(...args).then((ret) => {
            lastArgs = args;
            lastResult = new Promise((res) => res(ret));
            return ret;
        });
    };
}

I think it makes sense for this to be within the library itself. Rather than being tied to redux and its store, this library also makes sense as simply "save computations by making a function return a precomputed value if given repeated inputs". Within that scope, having a Promise compatible selector makes sense.

All 9 comments

Reselect is not intended for this use case, it is for computed derived data from data that is already in the store. Try looking at Redux Thunk or, if more complicated orchestration is required, Redux Saga or Redux Loop.

Thanks for coming back to me, apologies for replying late.

Unless I'm wrong, none of redux-thunk, redux-saga or redux-loop fit my requirements as they all end-up updating the state with the asynchronous data.

In my case, storing the itineraries in the store doesn't make sense, as itineraries result from the actual data in my store. itineraries = f(store);

The only thing I'd like to control is when they shall be recalculated.

After some deep thought about it, my conclusion is that the logic needs to lie in the component itself: componentDidUpdate will have to check whether itineraries are up-to-date and recalculate those if they need to.

...which felt like this is what reselect does, at least synchronously !

So regarding my case, wouldn't it make sense to handle asynchronous selects? How would you deal with it otherwise?

Cheers

I had a similar use case, and I ended up having to copy the main reselect selector code and alter it for promises.

function memoizeLastPromise(func) {
    let lastArgs = null;
    let lastResult = null;
    return (...args) => {
        if (
            lastArgs !== null &&
            lastArgs.length === args.length &&
            args.every((value, index) => value === lastArgs[index])
        ) {
            return lastResult;
        }
        return func(...args).then((ret) => {
            lastArgs = args;
            lastResult = new Promise((res) => res(ret));
            return ret;
        });
    };
}

I think it makes sense for this to be within the library itself. Rather than being tied to redux and its store, this library also makes sense as simply "save computations by making a function return a precomputed value if given repeated inputs". Within that scope, having a Promise compatible selector makes sense.

I'd be curious to have @ellbee feedback on this, but in case he stands on his position, would you consider forking the repo @samiskin with you custom code ?

I don't know enough how the library works yet to understand your code, but it shall become clearer at some point.

If you do async calls in the selector it is not going to play nicely with Redux Devtools, which achieves time travel by replaying a series of actions. As Reselect is primarily a companion library for Redux I don't think it is a good idea to introduce anything that breaks Devtools or encourages non-idiomatic patterns.

That said, createSelectorCreator, with its swappable memoize function, was introduced to the library to make what you are requesting possible without having to maintain a fork. Just create a new selector creator that swaps defaultMemoize for memoizeLastPromise. You could create a module that wraps Reselect to introduce the new selector type, or a module that just exports memoizeLastPromise

+1 Agreed that this would be a very useful addition, so it would be nice to see this reconsidered.

Selectors have never been meant for asynchronous behavior - they're intended for synchronously reading values for a state tree, with memoization of derived results.

Found this project https://github.com/humflelump/async-selector that solves this issue.

Hey, I made a small hook https://www.npmjs.com/package/use-async-selector that could help.

Was this page helpful?
0 / 5 - 0 ratings