Momen Zalabany
Momen Zalabany

Reputation: 9007

typescript how to type a Proxy that curry its props and inject a prop

i have a helper function that take and Object of functions and return a proxy of that object with its functions composed with a first arg injected.

better shown in code:-

// example of object
export const selectors = {
  selectA: (state:State) => state.a,
  addToA : (state:State, num:number) => state.a + num
}

// ... in another file my helper fn

import {useSelector} from 'react-redux';

// PROBLEM IS HERE...
export const function bindSelector(obj){
  return new Proxy(obj,{
    get: (main, key, ctx) => {
      const fn = main[key].bind(ctx);

      // always inject store as first prop of fn
      // we get store arg from useSelector higher order (its already properly typed)
      return props => useSelector( store=> fn(store, props) )
    }

  })
}

export default bindSelector(selectors);

so i use Proxy so that i dont have to include useSelector, or pass store param to every selector when i use it

example usage of this would be

import selectors from './a';

// now i can just
const a = selectors.selectA(); // give error param missing
const aAnd2 = selectors.addToA(2); // how to tell typescript that this first arg is in fact the 2nd arg :D !.

Problem

  1. if i type bindSelector to return same type of (obj) then i get error of selecte require 1 param because typescript doesnot know that my proxy already supplied first param to function.

Upvotes: 0

Views: 516

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249686

You can use some mapped and conditional types to map the original object to a new object where each function has one less argument:


import { useSelector } from 'react-redux';

type State = {
  a: number
}
// example of object
export const selectors = {
  selectA: (state: State) => state.a,
  addToA: (state: State, num: number) => state.a + num
}

type CurriedFunctionObject<T> = {
  [P in keyof T]: T[P] extends (s: State, ...p: infer P) => infer R ? (...p: P) => R : never
}

export function bindSelector<T extends Record<string, (...a: any) => any>>(obj: T): CurriedFunctionObject<T> {
  return new Proxy(obj, {
    get: (main, key, ctx) => {
      const fn = main[key as keyof T].bind(ctx);

      // always inject store as first prop of fn
      // we get store arg from useSelector higher order (its already properly typed)
      return (args: any[]) => useSelector(store => fn(store, ...args))
    }

  }) as any as CurriedFunctionObject<T>
}

const cSelectors = bindSelector(selectors);
const a = cSelectors.selectA(); // give error param missing
const aAnd2 = cSelectors.addToA(2)

Play

You can replace State with any in the conditional type in order to make the type work for any state.

Upvotes: 1

Related Questions