Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Created March 9, 2019 22:50
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save busypeoples/6ac09e8dd63a12f78603fd76aa9cf580 to your computer and use it in GitHub Desktop.
Save busypeoples/6ac09e8dd63a12f78603fd76aa9cf580 to your computer and use it in GitHub Desktop.
Infer Props using PropTypes.InferProps
import React from "react";
import { render } from "react-dom";
import PropTypes from "prop-types";
// Using PropTypes.InferProps
type InferPropTypes<
PropTypes,
DefaultProps = {},
Props = PropTypes.InferProps<PropTypes>
> = {
[Key in keyof Props]: Key extends keyof DefaultProps
? Props[Key] | DefaultProps[Key]
: Props[Key]
};
// Test
const userPropTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
active: PropTypes.bool
};
const userDefaultProps = {
name: "Test"
};
type ActionProps = InferPropTypes<
typeof userPropTypes,
typeof userDefaultProps
>;
/*
type ActionProps = {
id: number;
name: string;
active?: boolean;
}
*/
const Action = (props: ActionProps) => {
return (
<div>
id: {props.id}
name: {props.name}
status: {props.active ? "active" : "inactive"}
</div>
);
};
Action.defaultProps = userDefaultProps;
render(<Action id={1} />, document.getElementById("root"));
@flying-sheep
Copy link

flying-sheep commented Nov 10, 2021

Improvement to not add spurious nulls (for strict null checks):

 import { InferProps } from "prop-types";

 type InferPropTypes<
   PropTypes,
   DefaultProps = {},
   Props = InferProps<PropTypes>
  > = {
-  [Key in keyof Props]: Key extends keyof DefaultProps
+  [Key in keyof Props]-?: Key extends keyof DefaultProps
-    ? Props[Key] | DefaultProps[Key]
+    ? NonNullable<Props[Key]> | DefaultProps[Key]
     : Props[Key]
 };
  • The -? makes it so props aren’t automatically unified with | undefined
  • The NonNullable makes it so that only if the defaultProps entry is null or undefined will the actual type be null. This enables e.g.:
const myInputPropTypes = { onChange: PropTypes.func }
const myInputDefaultProps = { onChange: (value: string) => {} }
type MyInputProps = InferPropTypes<typeof myInputPropTypes, typeof myInputDefaultProps>

const MyInput = ({ onChange }: MyInputProps) => {
  // TypeScript will not complain that `onChange` might be nullish here!
  const handleChange = (e) => onChange(e.currentTarget.value);
  return ... onChange={handleChange} ...;
};

ideal also for js migration!

helpers.d.ts

declare global {
  type InferPropTypes<PropTypes, DefaultProps = {}, Props = InferProps<PropTypes>> = {
    [Key in keyof Props]-?: Key extends keyof DefaultProps ? NonNullable<Props[Key]> | DefaultProps[Key] : Props[Key]
  }
}

SomeComponent.js

/** @param {InferPropTypes<typeof someComponentPropTypes, typeof someComponentDefaultProps>} props */
const SomeComponent = ({ ... }) => { ... }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment