epitath
epitath copied to clipboard
Typescript support
I've been looking into adding Typescript support to this library. Here are the issues I encountered.
-
JSX factory invocations (eg:
<Foo />) returnReactElement<any>(but a fix is on the way), which means we can't infer the prop types of any returned elements -
This is only true if you explicitly type your stateless component using@types/reactdoes not allow thechildrenprop to be a function (at least with "stateless components")React.SFC -
Typescript cannot infer what
yieldreturns, which means render props are untyped by default https://github.com/Microsoft/TypeScript/issues/26959
Now I'm wondering if "yield to wrap the next yield or return" is worth the lack of type safety, or if I should bite the bullet on using render props without this library. 😞
If generators were always immutable in Javascript, this would be a lot easier.
The best workaround is something like:
import { Component, ReactNode } from 'react'
interface ParentProps<T extends object = any> {
children?: ReactNode[] | ((props: T) => ReactNode)
}
// Infer the argument types of a function
type In<T> = T extends (...args: infer U) => any ? U : []
/** The functional "render children" prop */
type ChildFunction<T extends ParentProps> = Exclude<
T['children'],
ReactNode[] | undefined
>
// Component with render props
type RPC = Component<ParentProps> | ((props: ParentProps) => ReactNode)
/** Extract the render props of a component */
export type RenderProps<T extends RPC> = In<
ChildFunction<T extends Component<ParentProps> ? T['props'] : In<T>[0]>
>[0]
And then:
import { Component, ReactNode } from 'react'
import epitath, { RenderProps } from 'epitath'
declare const Foo: (props: FooProps) => ReactNode
declare type FooProps = {
children?: (value: { a: number }) => ReactNode
}
declare class Bar extends Component<BarProps> {}
declare type BarProps = {
children?: (value: { b: number }) => ReactNode
}
const App = epitath(function* () {
const { a }: RenderProps<typeof Foo> = yield <Foo />
const { b }: RenderProps<Bar> = yield <Bar />
return a + b
})
Example: https://codesandbox.io/s/qqonn05154?view=editor (note: CodeSandbox is using TS 2.7 so not exactly a working example right now)
That is interesting! I don't know much about TS myself, but I'm going to read more about those and see if we can ship support right away
Nice. Also, I opened https://github.com/Microsoft/TypeScript/issues/27267 to see if better support for immutable generators could be added.
Just to share my config which works so-and-so (having typing issues with yielded elements not having an explicit children prop)
declare module "epitath" {
import * as React from "react"
const epitath: <P extends {}>(
Component: (props: P) => IterableIterator<React.ReactElement<any>>
) => React.ComponentType<P>
export default epitath
}
// interface Props { foo: string }
// Usage: const FooComponent = epitath<Props>(function* ({ foo }) { ... })
FYI meanwhile we discuss about this, ReasonML implementation of Epitath gave free type inference support https://medium.com/astrocoders/render-props-composition-for-reasonml-is-here-b9c004ca9fcb