DEV Community

Cover image for Introducing The Recursive `Pipe` and `Compose` Types

Introducing The Recursive `Pipe` and `Compose` Types

Babak on April 12, 2019

It turns out, the recursive Pipe (and Compose) types offer key advantages over the traditional method of using parameter overloading. The key advan...
Collapse
 
ackvf profile image
Vítězslav Ackermann Ferko • Edited

Hi, at first I would like to say that I like your approach the most of all I've seen.

My question, coming from React, is:
Would it be possible to compose functions in a way, that any extra incoming arguments are passed along with current's hoc outgoing arguments to next hoc, and that any extra arguments of the wrapped function that are not satisfied by any hoc are also exposed in the outer interface?

In React it makes sense as higher order components/functions usually don't represent a chain of actions on a single value, but instead add some functionality and add additional props to the underlying consumer component. Though, it's all in a single object called props.

It's somewhat difficult to express clearly, so here's the idea:

interface InnerProps { c: number, d: number }
interface H1in { a: number }
interface H1out { b: number }
interface H2in { b: number }
interface H2out { c: number }

declare const WrappedComponent: (props: InnerProps) => any // React component
declare const H1: (props: H1in) => H1out & H1in // also must pass rest properties ({a, ...rest}) => ({b: a + 10, ...rest})
declare const H2: (props: H2in) => H2out & H2in // ...rest properties

const OuterComponent: (props: OuterProps) => any = pipe(H1, H2)(WrappedComponent)
/*
interface OuterProps { 
  a: number // from H1
  d: number // WrappedComponent own props that are not satisfied by any available HOC
  // b, c are not exposed as they are satisfied from the chain
}
*/

A real life usage could be

interface MyComponentProps { giveMeThisProp: any }
declare const MyComponent: React.FC<MyComponentProps & Theme & Query>

pipe(
  withSettings,
  withApolloQuery(query),
  withTheme
)(
  MyComponent
)

Where the MyComponent doesn't really care about what withSettings returns, but withApolloQuery needs it. MyComponent then cares about the result, theme and the consumer should provide the additional required prop: <MyComponent giveMeThisProp={true} />

Collapse
 
lexlohr profile image
Alex Lohr

Great article and wow, what a complex and powerful type! Is there a way to get the actual argument (and not its type) recursively? I'm currently trying to create such a type for Solid.js' setStore function, which has basically the following interface:

setStore<Store>(
  ...selectors: StoreSelectors<Store¹>[],
  setter: StoreSetter<Store²>
) => void
Enter fullscreen mode Exit fullscreen mode

A selector can be a keyof Item, (keyof Item)[], a range { from: number, to: number } or a filter function. A setter can either be a DeepPartial value of the selected type, undefined or a function that receives the current Item and returns aforesaid values.

¹: each subsequent selector should not receive the Store, but the selection within it.

²: the setter should receive the selection of the last selector.

However, a selector could also be a string or number if the selected item inside the store was an object or array. Unfortunately, if I use

export type SetStoreFunction<Store> = <Args extends any[]>(
  ...args: Args & SetStoreArgs<Store, Args>
) => void;

setStore<{ test: 1, test2: { test3: 2 }}>('test2', 'test3', 3);
Enter fullscreen mode Exit fullscreen mode

I get the error that my Selectors are matching undefined. Any ideas or pointers on what I'm doing wrong?