Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Improvement/Addition] Extracting and composing types using $ElementProps #23

Closed
dmisdm opened this issue Jun 8, 2018 · 6 comments
Closed
Labels
wontfix This will not be worked on

Comments

@dmisdm
Copy link

dmisdm commented Jun 8, 2018

Just thought i'd note something that i do, that may be useful for others.
Since TS 2.8's conditional types, its possible to extract a type parameter from another type (although maybe it was possible before but i didn't know how to do it).
This is essentially a port of flow's react utility type "ElementProps".
It allows you to extend another component easily, which assists with typing HOCs, and props spread.

eg:

// ReactUtilityTypes.d.ts
declare type $ElementProps<T> = T extends React.ComponentType<infer Props>
  ? Props extends object ? Props : never
  : never;

Then say you have some components which spread props to its child (almost like defaultProps, mapProps, withProps HOCs)
https://github.com/acdlite/recompose/blob/master/docs/API.md#withprops

//Components.tsx

/*
Std box component.
Eg <Box  display="flex" justifyContent="center" color="palevioletred" />
*/
const Box = (props: React.CSSProperties) => <div style={props} />

const Card =  ({title, children, ...props}: {title: string} & $ElementProps<typeof Box>) =>
<Box {...props}>{title}: {children}</Box>

We could have just used React.CSSProperties in place of $ElementProps<...>, but thats not really what we're trying to express. What if the component was from a 3rd party library and you didn't know where the Props interface was?
Eg:

import Paper from '@material-ui/core/Paper';

const Card =  ({title, children, ...props}: {title: string} & $ElementProps<typeof Paper>) =>
<Paper {...props}>{title}: {children}</Box>

Not only that, you can use https://github.com/piotrwitek/utility-types to express even more:

import Paper from '@material-ui/core/Paper';
import {Omit} from 'utility-types'

// Same as above except cannot be styled
const Card =  ({title, children, ...props}: {title: string} & Omit<$ElementProps<typeof Paper>, "style" | "className" | "classes">) =>
<Paper {...props}>{title}: {children}</Box>

Sorry if this is not succinct, just wasn't sure which parts are worth going in the cheatsheet or if other people would find this valuable.

Happy to write something more cheatsheet worthy or presentable if needed!

@swyxio
Copy link
Collaborator

swyxio commented Jun 8, 2018

This is very interesting and very appreciated, thank you! can I just ask a clarifying question - in your import Paper from '@material-ui/core/Paper'; example, does that work even if Paper is untyped?

or are you basically saying, ok, @material-ui/core/Paper doesn't export Paper's proptypes, but that's ok, because I can just use $ElementProps to grab it and do manipulations like & and Omit?

Just trying to explain back to you what i think this does. do i have the right idea?

@dmisdm
Copy link
Author

dmisdm commented Jun 8, 2018

Yeah thats about right!
Its a nice solution to when you say "I want this prop (eg paperProps) or these props to spread to this component, but not have to decide on the type (be explicit)".
The less explicit about types you are, the safer it will be (imo). Especially when there are any's flying around.

If Paper doesn't have a type because its referencing a JS file (it does though - from material-ui) then TS treats them as any, so yeah it'l work, like everything does with any haha.

It just lets you be a bit more expressive, less explicit, and helps in other situations too, especially with making HOCs - when Component or Props types are generic.
Eg - making recompose's defaultProps work properly with TS (we can't preserve currying though, cause no higher kinded types in TS)

import * as Recompose from 'recompose'
export const defaultProps = <
  C extends React.ComponentType,
  D extends Partial<$ElementProps<C>>
>(
  defaults: D,
  Component: C
): React.ComponentType<$ElementProps<C> & Partial<D>> =>
  Recompose.defaultProps(defaults)(Component);

Make sense?

@swyxio
Copy link
Collaborator

swyxio commented Jun 13, 2018

yes - am very busy this week so will come back and review this next week. thanks!

@stale
Copy link

stale bot commented Aug 20, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions!

@stale stale bot added the wontfix This will not be worked on label Aug 20, 2020
@swyxio
Copy link
Collaborator

swyxio commented Aug 20, 2020

included in #272

@swyxio swyxio closed this as completed Aug 20, 2020
@bsitruk
Copy link

bsitruk commented May 16, 2021

Hi,

Is $ElementProps still relevant ?
Isn't React.ComponentProps doing the same job ?

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

3 participants