question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Help with Typescript and children types

See original GitHub issue

Hey there,

I have strictly typed my children prop for my render prop components. Now, I’m trying to integrate react-adopt@0.6.0, and I’m having some issues with those typings.

I have a render prop component that looks like this:

//Component.tsx
export interface Props {
  children: (track: Track) => JSX.Element
}

export default function({ children }: Props) {
  return children((event, data) => {
    ...
  })
}

When I compose that with react-adopt, like this:

const Composed = adopt<RenderProps, ComposedProps>({
  track: () => (
    <Component/>
  ),
...

I get a typescript error that children is missing.

If I type the children prop in the Component in the same way it’s typed in react-adopt: ((track: Track) => JSX.Element | null) | undefined, I need to update the component to return children && children(...) to satisfy the compiler.

At that point I start receiving this warning inside the composed function, for my Component: JSX element type 'Element | null | undefined' is not a constructor function for JSX elements. Type 'undefined' is not assignable to type 'ElementClass'.

When I assume I need to follow your typescript example more closely, I tried:

const Composed = adopt<RenderProps, ComposedProps>({
  track: ({ render}) => (
    <Component> {(track => render({track})} </Component>
...

This returns the typescript error: Cannot invoke an object which is possibly 'undefined'.

I’ve tried again typing the Component children type to include undefined, as per react-adopt, as well as updating the composed Component to use render && render(...), returns the above error again on the component being composed: JSX element type 'Element | null | undefined' is not a constructor function for JSX elements. Type 'undefined' is not assignable to type 'ElementClass'.

I normally wouldn’t bother maintainers with typescript questions, but since you’ve written the source in typescript I thought this might have been a problem that you ran into and accounted for, in which case I’m sure I’m missing something simple here!

I’d appreciate any help getting around this issue! Thanks in advance for your time 😄

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:12 (1 by maintainers)

github_iconTop GitHub Comments

5reactions
pedronauckcommented, May 21, 2018

Did you try to use your children function as an optional type like that:

import React, { SFC } from 'react'

//Component.tsx
export interface Props {
  children?: (track: Track) => JSX.Element
}

const Component = SFC<Props> = ({ children }) =>
  children &&
  typeof children === 'function' &&
  children((event, data) => {
    ...
  })

export default Component

When this happened with me that was my solution ✌️

2reactions
clayne11commented, Jul 27, 2018

@damassi here is the proof of concept for the types:

/// <reference types="react" />
import React from 'react'
import {ReactElement} from 'react'

type SafeOmit<T, K> = Pick<T, Exclude<keyof T, K>>

export declare type ChildrenFn<P> = (props: P) => JSX.Element | null
export declare type RPC<RP, P = {}> = React.ComponentType<
  P & {
    children?: ChildrenFn<RP>
    render?: ChildrenFn<RP>
  }
>
export declare type MapperComponent<RP, P> = React.ComponentType<
  RP &
    P & {
      render?: ChildrenFn<any>
    }
>
export declare type MapperValue<RP, P> =
  | ReactElement<any>
  | MapperComponent<RP, P>

export declare type MapProps<RP> = (props: any) => RP

export declare type AdoptProps<RP, P> = P & {
  mapper: Mapper<RP>
  children?: ChildrenFn<RP>
  render?: ChildrenFn<RP>
  mapProps?: MapProps<RP>
}
export declare class Adopt extends React.Component<AdoptProps<any, any>> {
  private Composed
  constructor(props: any)
  render(): JSX.Element
}

export declare function adopt<RP = any>(
  mapper: RP,
  mapProps?: MapProps<RP>
): RPC<Mapper<RP>, ComponentProps<RP>>

// extract the render props param from a given render function
export type RenderPropsInput<T> = T extends (
  params: {
    render: (params: infer P) => React.ReactNode
  }
) => any
  ? P
  : never

// map over each key in the mapper object and set the right hand side equal to its 
// respective render props input
export type Mapper<RP> = {[K in keyof RP]: RenderPropsInput<RP[K]>}

// extract the first argument to any function
export type FnParams<T> = T extends (params: infer P) => any ? P : any

// to get the props the composed component will need,
// take all the props required by all the render props functions and exclude the render
// function (since its provided by `react-adopt`) and also omit all the keys
// from the mapper object since they are injected into each other
type ComponentProps<RP> = SafeOmit<AllProps<RP>, 'render' | keyof RP>

// take all the input keys from the mapper and set the right hand side
// equal to the function parameters for that key,
// then make a union out of all the values in the object
type AllProps<RP> = UnionToIntersection<
  {[K in keyof RP]: FnParams<RP[K]>}[keyof RP]
>

// take a union type (A | B | C) and convert it to an intersection (A & B & C)
type UnionToIntersection<U> = (U extends any
  ? (k: U) => void
  : never) extends ((k: infer I) => void)
  ? I
  : never

Currently it only support passing a function that accepts render, but I don’t foresee any issues extending it to support components with render props.

If you use these types all you need to do is call adopt({...}) and it will infer both the render props types and the input props into the composed component.

So far I’ve also only typed the adopt function, not the component, although I also don’t see an issue typing the component in the same manner.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How Children Types Work In React 18 And TypeScript 4
What types can be react children · string literals (also includes numbers which are coerced to HTML strings) · JSX children (aka, nested...
Read more >
React Children with TypeScript | Building SPAs - Carl Rippon
The React children prop allows components to be composed together and is a key concept for building reusable components.
Read more >
What is the type of the 'children' prop? - Stack Overflow
you should know that any react component should return null or React.Element , but the type of props.children is React. · the second...
Read more >
TypeScript and React Children - DEV Community ‍ ‍
What happens when we pass in children in React? Children is a special prop that allows us to pass in any type of...
Read more >
TypeScript and React: Children - fettblog.eu
In React, children elements are accessible via the children props in each component. With TypeScript, we have the possibilty to make children type...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found