-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
Implementing defaultProps with ts 2.0 strict null checks #11640
Comments
Since the default props are set at runtime, I'm not sure if there's a way to handle this nicely, other than a type assertion. (Of course, you could always just disable strict null checks.) Here's how you may be able to get around it in your example:
See https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions If there's a more graceful way of handling this, I'd love to hear it. Disclaimer: I've been using TypeScript for about three days, I'm tired, and probably have no idea what I'm talking about. |
+1000 on updating type definition file to include three generic type definitions. This used to be fine without Flow also implements similar class implementation because of nature of strict null type checking. |
Seems like we don't have much options here except of waiting for microsoft/TypeScript#2175 being resolved in order to add third generic. |
@R00GER agreed. Changing the definition is too disruptive. Has anyone considered using As in: interface ComponentClass<P> {
- defaultProps?: P;
+ defaultProps?: Partial<P>;
} |
Don't mind that |
* upgrade antd-tools for new tslint * Auto fix tslint errors * fix: tslint * fix: strictNullCheck with defaultProps DefinitelyTyped/DefinitelyTyped#11640 * fix: tslint-fix
Partial only solves how to declare partial propTypes problem. Inside of interface IUser {
firstName: string
lastName?: string
}
export class User extends React.Component<IUser, {}> {
public static defaultProps: Partial<IUser> = {
lastName: 'None',
}
public render () {
const { firstName, lastName } = this.props
// error
lastName.toUpperCase()
return (
<div>{firstName} {lastName}</div>
)
}
} I've just started using TS. Am I missing anything? |
If anyone has a good solution for types and defaultProps, I'm all ears. We currently do this: interface Props {
firstName: string;
lastName?: string;
}
interface DefaultProps {
lastName: string;
}
type PropsWithDefaults = Props & DefaultProps;
export class User extends React.Component<Props> {
public static defaultProps: DefaultProps = {
lastName: 'None',
}
public render () {
const { firstName, lastName } = this.props as PropsWithDefaults;
return (
<div>{firstName} {lastName}</div>
)
}
} |
+1 |
+1 |
+1 |
In addition to adding the third type parameter, you'll need the ability to diff props against the defaulted props. Happily as of TS 2.4 this is now possible! See microsoft/TypeScript#12215 (comment) |
IMHO adding third parameter is a big no no, also Flow team knew that and recently they changed that for greater good. It should be type-checker responsibility to know how to handle these kind of things. Don't get me wrong, I love Typescript, but since Flow 0.53 I have to say it's superior for React development https://medium.com/flow-type/even-better-support-for-react-in-flow-25b0a3485627 |
@Hotell Flow does have three type parameters for |
nope, it used to be that way before 0.53, not anymore :) facebook/flow@20a5d7d#diff-5ca8a047db3f6ee8d65a46bba4471236R29 |
@Hotell Ah, sure enough! Thanks for correcting me. AFAIK there's no way in TS to infer the type of the default props though. Using the three-type-parameter approach we'd likely be able to get correct typing without blocking on upstream changes from the TypeScript team. Do you know of a way to use the inferred type of a static property without passing |
Any news on this subject? Does someone do a PR to add a third type parameter and use microsoft/TypeScript#12215 (comment)? |
Upvoting issue: same problem |
+1 |
I also ran into this, and I've chosen (until this is properly fixed) to abstain from using File components/helpers/withDefaults.tsx: import * as React from 'react'
export interface ComponentDefaulter<DP> {
<P extends {[key in keyof DP]?: any}>(Component: React.ComponentType<P>): React.ComponentType<
Omit<P, keyof DP> & // Mandate all properties in P and not in DP
Partial<Pick<P, keyof DP>> // Accept all properties from P that are in DP, but use type from P
>
}
export default function withDefaults<DP>(defaultProps: DP): ComponentDefaulter<DP> {
return Component => props => <Component {...defaultProps} {...props}/>
} Now I can use: File components/Button.tsx: import * as React from 'react'
import withDefaults from './helpers/withDefaults'
export interface ButtonProps {
label: string
onPress: () => any
}
export const defaultProps = {
onPress: () => undefined
}
class Button extends React.Component<ButtonProps> {
// ...
}
export default withDefaults(defaultProps)(Button) Three potential downsides (that I can think of):
|
According to @vsaarinen, I write a base class with Like this: import * as React from 'react'
export class Component<P = {}, S = {}, DP = {}> extends React.Component<P, S> {
props: Readonly<{ children?: React.ReactNode }> & Readonly<P> & Readonly<DP>
}
export interface Props {
firstName: string
lastName?: string
}
export interface DefaultProps {
lastName: string
}
export class User extends Component<Props, any, DefaultProps> {
render() {
const { firstName, lastName } = this.props
// no error
return (
<div>{firstName} {lastName.toUpperCase()}</div>
)
}
} |
Actually @qiu8310 that didnt work fully, Still had issues with call sites screaming about those default props not being optional. Got it to work with a minor adjustment import * as React from 'react'
export class Component<P = {}, S = {}, DP = {}> extends React.Component<P, S> {
// Cast the props as something where readonly fields are non optional
props = this.props as Readonly<{ children?: React.ReactNode }> & Readonly<P> & Readonly<DP>
}
export interface Props {
firstName: string
lastName?: string
}
export interface DefaultProps {
lastName: string
}
export class User extends Component<Props, any, DefaultProps> {
render() {
const { firstName, lastName } = this.props
// no error
return (
<div>{firstName} {lastName.toUpperCase()}</div>
)
}
} |
interface Component<P = {}, S = {}, DP extends Partial<P>=P> extends ComponentLifecycle<P, S> { }
class Component<P, S, DP extends Partial<P> = P> {
constructor(props: P & DP, context?: any);
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
forceUpdate(callBack?: () => void): void;
render(): ReactNode;
// React.Props<T> is now deprecated, which means that the `children`
// property is not available on `P` by default, even though you can
// always pass children as variadic arguments to `createElement`.
// In the future, if we can define its call signature conditionally
// on the existence of `children` in `P`, then we should remove this.
private __externalProps: Readonly<{ children?: ReactNode }> & Readonly<P>;
props: Readonly<{ children?: ReactNode }> & Readonly<P> & DP;
state: Readonly<S>;
context: any;
refs: {
[key: string]: ReactInstance
};
}
class PureComponent<P = {}, S = {}, DP extends Partial<P>=P> extends Component<P, S, P> { }
interface ElementAttributesProperty { __externalProps: {}; } Look carefully at last line. With this changes we could have interface Props {
a: string
b?: string
c?: string
}
class Comp extends React.Component<Props, {}, typeof Comp.defaultProps> {
static defaultProps = {
b: ''
}
render() {
const {a, b, c} = this.props
let res = a.concat(b) // ok
let res1 = a.concat(c) //fail
return null
}
}
const res1= <Comp a=''/> // ok
const res3 = <Comp /> // fail Which is best we can get if using |
Here's my (very ugly) attempt based on the idea from https://medium.com/@martin_hotell/ultimate-react-component-patterns-with-typescript-2-8-82990c516935: type ExtractProps<T> = T extends React.ComponentType<infer Q> ? Q : never;
type ExtractDefaultProps<T> = T extends { defaultProps?: infer Q } ? Q : never;
type RequiredProps<P, DP> = Pick<P, Exclude<keyof P, keyof DP>>;
type RequiredAndPartialDefaultProps<RP, DP> = Required<RP> & Partial<DP>;
type ComponentTypeWithDefaultProps<T> =
React.ComponentType<
RequiredAndPartialDefaultProps<
RequiredProps<ExtractProps<T>, ExtractDefaultProps<T>>,
ExtractDefaultProps<T>
>
>;
function withDefaultProps<T extends React.ComponentType<any>>(Comp: T) {
return Comp as ComponentTypeWithDefaultProps<T>;
} interface IProps {
required: number;
defaulted: number;
}
class Foo extends React.Component<IProps> {
public static defaultProps = {
defaulted: 0,
};
}
// Whichever way you prefer... The former does not require a function call
const FooWithDefaultProps = Foo as ComponentTypeWithDefaultProps<typeof Foo>;
const FooWithDefaultProps = withDefaultProps(Foo);
const f1 = <FooWithDefaultProps />; // error: missing 'required' prop
const f2 = <FooWithDefaultProps defaulted={0} />; // error: missing 'required' prop
const f3 = <FooWithDefaultProps required={0} />; // ok
const f4 = <FooWithDefaultProps required={0} defaulted={0} />; // ok |
@decademoon, seems like we could just use this solution at |
@decademoon your definition doesn't handle the case where the non-default props actually include optional fields, i.e.
I got it working in my case by changing your RequiredAndPartialDefaultProps type to not wrap the "RP" with "Required"
|
I'm surprised there still isn't a proper solution or at least a working HOC on NPM; unless I have missed something. |
Hi everyone. Just wanted to say and if you are still reading this thread: I think @JoshuaToenyes made the most meaningful and helpful explanation. This is definitely not an issue so there's nothing to do with it. Use type assertion in this case. |
@toiletpatrol actually, @decademoon's solution (with my slight amendment) automatically handles default props just fine. It definitely could be merged into the DT definitions for React to provide the feature standard to everyone. |
@toiletpatrol @RobRendell did you see it microsoft/TypeScript#23812? |
@vkrol I did see that, but I can drop decademoon's implementation in my codebase right now without waiting for releases of new features. |
Another workaround that I'm using for now for tricky cases: const restWithDefaults = { ...Component.defaultProps, ...rest };
return <Component {...restWithDefaults} />; Nothing wrong with it I guess, so I'm leaving it here as a dirty yet simple workaround. |
TS 3.2 and react 16.7 typings are fixing this. can we close ? |
Handling 'possibly undefined or null' type error for component's defaultProps: DefinitelyTyped/DefinitelyTyped#11640 (comment)
@Hotell how should it be handled eventually? I still can't get this working properly |
To save others some time, here is a link to the release notes of Typescript 3: |
@cbergmiller I am afraid those are the release notes for TypeScript 3.1 🙃 |
still having the same issue with |
@denieler I wouldn't advise using interface HelloProps {
name?: string;
surname?: string;
}
const HelloComponent: React.FunctionComponent<HelloProps> = ({
name = 'John',
surname = 'Smith',
}) => {
return <div>Hello, {name} {surname}!</div>
}; |
@mgol How would you define default function parameters if I didn't want to destructure the props? interface HelloProps {
name?: string;
surname?: string;
}
const HelloComponent: React.FunctionComponent<HelloProps> = ({
name = 'John',
surname = 'Smith',
...props
}) => {
return <div>Hello, {name} {surname}! You are {props.age} years old.</div>
}; But I find it disgraceful to extract only some of the props. |
@glecetre You can use: HelloComponent.defaultProps = {
name: 'John',
surname: 'Smith'
} |
@glecetre Why do you not want to destructure all the props? It's simpler than defining |
Hi thread, we're moving DefinitelyTyped to use GitHub Discussions for conversations the To help with the transition, we're closing all issues which haven't had activity in the last 6 months, which includes this issue. If you think closing this issue is a mistake, please pop into the TypeScript Community Discord and mention the issue in the |
Default properties don't seem to function well currently with strictNullChecks enabled. For example:
Errors with
error TS2322: Type 'number | undefined' is not assignable to type 'number'
even though this is guaranteed to work at runtime.Right now defaultProps and Props seem to be treated as the same type always, but they're actually almost never the same type because optional fields in Props should be overwritten by required values in DefaultProps.
What if there were something like...
that is identical to the existing React.Component typing except for the type of props?
The text was updated successfully, but these errors were encountered: