Matt Sieker
Matt Sieker

Reputation: 9635

Inferring return type from createSelector factory

I'm trying to integrate reselect into some redux connected components. According to the docs for reselect (https://github.com/reduxjs/reselect#sharing-selectors-with-props-across-multiple-component-instances), when dealing with props passed in, to ensure the selector is properly memoized, you basically create a factory function for createSelector, so every component calling connect() gets its own selector with its own memoized state. This makes sense, and I have it working in TypeScript:

interface UploadProfileOwnProps {
    someValue: string;
}

const getUploadProfile = (state: AppState, props: UploadProfileOwnProps): UploadProfileState => state.uploadProfile;
const getCustomer = (state: AppState, props: UploadProfileOwnProps): CustomerState => state.customer;

const uploadProfileSelectorFactory = () => createSelector(getCustomer, getUploadProfile, 
    (customerState, profile)=>({
        customerId: customerState.customerId,
        ...profile
    }));

type uploadProfileSelectorProps = ReturnType<ReturnType<typeof uploadProfileSelectorFactory>>;

const connectUploadProfile = connect(()=>{
    const selector = uploadProfileSelectorFactory();
    const mapStateToProps = (state: AppState, props: UploadProfileOwnProps): uploadProfileSelectorProps =>{
        return selector(state, props);
    };
    return mapStateToProps;
}, UploadProfileActions);

The OwnProps is just a placeholder, the state doesn't depend on it at all in this case, but in others it might. The double ReturnType<ReturnType<>> construct bugs me a bit, but it gets the job done. Now, the actual question:

uploadProfileSelectorFactory lacks an explicit return type. I have eslint configured to yell at me in that case. How can I convince TypeScript to tell it that the factory function's return type is whatever createSelector() returns? It does properly infer the return type, so it's not causing any issues outside of eslint being cranky. I see a few options:

  1. Just tell eslint to ignore the return type requirement for that line. While easiest, I don't really like it.
  2. Explicitly define the type that comes out of the combiner function, then have the factory function return the type of CreateSelector, which in this case would be a rather messy OutputParametricSelector<S, P, T, (res1: R1, res2: R2) => T> that I would have to remember to change if I add another selector.
  3. Somehow convince TypeScript to give me the return type of createSelector() without actually calling it. This might be able to be done with explicitly defining the return type of the combiner function, although I haven't tried it. This would probably also get rid of the ReturnType<ReturnType<>> business, at the cost of having me keep the combiner function and its return type in sync.
  4. Throw re-reselect or similar into the mix, and hope it doesn't have that issue.
  5. Invoke some TypeScript black magic that solves the whole thing.

I'm hoping for some combination of 5 and 3, although I'll take 4 if needed. Unfortunately every example I've found just uses type inference and never deals with this.

Upvotes: 1

Views: 2005

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42188

You do know what createSelector returns, because ultimately it's just a selector that selects the customerId and profile. The selectors created by reselect have to extra properties like recomputations and resetRecomputations, but these don't matter for the purposes of react-redux. We just want to know what arguments the selector takes and what it returns.

This is what you are selecting:

type Selected = UploadProfileState & Pick<CustomerState, 'customerId'>

So you can use that instead of ReturnType<ReturnType<typeof uploadProfileSelectorFactory>>

The return type of uploadProfileSelectorFactory would be:

(state: AppState, props: UploadProfileOwnProps) => Selected;

But that is long-winded and we have those same two arguments in multiple places, so if you want to you could define a helper type:

type UploadProfileSelector<Return> = (state: AppState, props: UploadProfileOwnProps) => Return;

or more generalized:

type ComponentSelector<Props, Return> = (state: AppState, props: Props) => Return;
type UploadProfileSelector<Return> = ComponentSelector<UploadProfileOwnProps, Return>;

Which you can use as the return type:

const uploadProfileSelectorFactory = (): UploadProfileSelector<Selected> =>

Upvotes: 1

Related Questions