So I have a component like this
type Props = {
name : string,
value : number,
tooltipValues? : Array<string>,
handleChange? : (value: number) => void
};
class Slider extends Component {
static defaultProps = {
tooltipValues : [],
handleChange : () => {}
};
state: State;
props: Props;
...
So I should be able to use handleChange without Flow warning about a possibly undefined value, but it still warns me. Is there something I'm doing wrong or a way around this?
Remove the optional in the Props.
Flow won't complain if you are missing handleChange due to the defaultProp.
Oh okay I see, thanks. But there is no way for it to work with optionals? I prefer it with optionals because it makes it clear within the context of the component, that those particular props are optional.
Well the thing is: as a type it is no longer optional.
There is no way for it to be not defined anymore.
Thats why the type is non optional now.
Ah okay, that makes sense. I guess the optional props would be more of a PropTypes use case.
Flow deals with Props and propTypes differently though
this type-checks
class A extends React.Component {
static defaultProps = {
name: 'Giulio'
};
static propTypes = {
name: React.PropTypes.string // <= not required
};
render() {
const name: string = this.props.name
return <div>{name}</div>
}
}
this doesn't
type Props = {
name?: string // <= not required
};
class B extends React.Component {
static defaultProps = {
name: 'Giulio'
};
props: Props;
render() {
const name: string = this.props.name
return <div>{name}</div>
}
}
@gcanti Isn't that because you should not be letting name be optional?
This typechecks for me:
// @flow
import React from 'react'
type Props = {
name: string // <= not required
}
class B extends React.Component {
static defaultProps = {
name: 'Giulio',
};
props: Props;
render() {
const name: string = this.props.name
return <div>{name}</div>
}
}
const hmm = <B />
console.log(hmm)
I've just tried this & it just works (adding = {}):
export default ({ children, title = 'This is the default title' }: {
children?: Element<any>,
title?: string
} = {}) => (
<div>Hello World</div>
)
I understand why from a type safety standpoint an optional prop that needs to be defined in defaultProps is technically not optional. But does that mean there is no way to use Flow to declare that the component consumer is not required to pass a certain prop, but that the prop will never be undefined?
I've also hit this, all my remaining flow warnings in my project are related to properties that are required, but have a default value.
I just did a quick hack to try to enforce DefaultProps to be compatible with Props.
In the React types:
// replace this:
declare class React$Component<DefaultProps, Props, State> { ... }
// with:
declare class React$Component<DefaultProps: $Shape<Props>, Props, State> { ... }
That seems to enforce Props and defaultProps to always be compatible.
Re-opening till I can test that fix more comprehensively.
@nmn where can this change be made manually, if I may ask?
@sebastienbarre You can't exactly do this without using the --no-lib flag and overriding the core type definitions for Flow.
@nmn
It would be nice if the core type definitions was updated.
@nmn - any chance to get this in? If I change my Props that are specified in DefaultProps to required, the rest of my lib then complains that the consumer hasn't specified the props - same as @dpehrson.
I just found something that works in simplistic but not all cases (and I'm not entirely clear why). Using type Props = DefaultProps & {...} works with PureComponent.
edit: see comment further down for test cases.
This has become really problematic converting a codebase to flow that has a great deal of stateless functions. So far, I need to hand-code the defaulting process so it passes flow, which doesn't seem feasible on a large scale.
Is there a recommended way to use defaultProps/optional Props with stateless functions that works?
An additional problem is if you're using https://github.com/brigand/babel-plugin-flow-react-proptypes to convert flow types to react propTypes, in which case you end up with required runtime propTypes which shouldn't be.
Also, out of curiosity, are people really redefining their propTypes in the react runtime form when already using Flow? I've not been doing so in application code, but in open source packages have been working around the above babel plugin that does the conversion for me. It has a few issues like the one I just mentioned. Are most people just redefining the propTypes a second time? Seems ridiculous to me.
I have just found a case the workaround I found (using a class and intersection) doesn't work at all. Perhaps it's a different bug like #3952, but it means I'm dead in the water converting to flow.
Latest test cases (in "try flow"):
PureComponent with intersection - works (for this specific case, but not all cases, see next)PureComponent with intersection of prop with union of Element<*> failsPureComponent without intersection - fails...but it means I'm dead in the water converting to flow.
You could use an error suppression comment until it's fixed.
This has been fixed in Flow v0.53. See an updated example here.
The syntax for attaching the prop types to the Component has changed, and many bugs about the way props and defaultProps interact have been fixed. The result is that you can make handleChange required in the prop types, but Flow is smart enough to see that a default value is provided in the defaultProps and know that code creating instances of the component do not have to pass in that key. (In a sense, the prop types are the "internal" types, what a component instance will actually see after default props have been filled in. The "outside" type, specifying which props are required to be passed in, is just the props type minus any keys that have defaults specified.)
I hope that's all clear! If not let me know and I'm happy to try to improve my explanation.
In the mean time, I think with Flow v0.53, this issue can be closed. (/cc @calebmer)
@asolove just to clarify with this example
Are you saying that the right way with flow 0.53+ is to not use the myProp?: number optional syntax? It seems like that was a pretty convenient way of visually understanding what props are required and which aren't (even if flow will do the work of combining Props and defaultProps)
I agree with you @johnryan - our material-ui/v1-beta library exports our Props for reuse in composing higher level components, and our intent is that our Props, just like the react component itself, indicate required and optional. Example of how we use it. Unfortunately this is more verbose than I think it should be.
It seems like an impedance mismatch the way the new 0.53.1+ props are a mix of default and props. I'd love to know a better way, but our export Props must exactly mirror the intent of the react components' usage.
The example you provided me looks like the output I would expect. A good way to think about this is that the Props in the function argument represents the "internal" type of the props, what the component has access to after defaults have been passed in.
If you want a way to refer to "the type of the props you have to pass in" the the component, you can do something like:
type RequiredProps = $Diff<OtherProps, typeof OtherSlider.defaultProps>;
Which, in this case, is equal to { size?: number }. Does that match the need you have?
Got it, yea the "internal" type makes sense. I've personally always used the props at the top of the file as sort of documentation on how to use that component when i look at the source, but I see now how it works.
Thanks for explaining that @asolove. I tried it and like @johnryan, I don't think it is as self documenting as it needs to be. I'd also wonder if react-docgen is going to pick up the proper maybe types here or not.
In any case, thank you, it does indeed work.
Yeah, definitely. I have a note to add this to the docs.
I need to say that the new way Flow handles default props worries me. 馃槦
FLOW IS A STATIC TYPE CHECKER FOR JAVASCRIPT
This is what the front page of Flow.org is saying. How "static" is a type when we're able to _dynamically_ change the "internal" type of a React component by just assigning another type to the default props than we did with the "external" type?
Something like ...
type RequiredProps = $Diff<OtherProps, typeof OtherSlider.defaultProps>
... is far away from the word "static". It means I'm able to change the value of a default prop of type _string_ to a value of type _boolean_ and it would completely change the "internal" type because of the dynamic inference. This would also lead Flow to complain about _used_ props and their types in the wrong place although it should already complain in place of the default props assignment.
The prop types I'm defining for a React component whether it is from the "outside" or the "inside" should all be the same.
Gentlemen, did someone found a better solution to type decently defaultProps without this verbosity and or flow/eslint warnings?
Thanks
"Gentlemen"?
@MaxInMoon Starting from Flow 0.53 just declare your defaultProps the same way as the required ones, without setting them as optional and Flow will do the rest :)
type Props = {
foo: string,
bar: string,
};
class Baz extends React.Component<Props> {
static defaultProps = {
bar: 'whatever',
};
}
All fine.
@pascalduez That works but what if we want to document that the prop is not required, without the consumer having to look at the component file?
@dtaub Write documentation for example or just place types and defaultProps near each other.
@dtaub See #5173 for more insights. Also as stated there tools like react-docgen will document default props properly.
Assume i has a component B
type Props = {
name: string // <= not optional because flow type knows it has default one so not going to be optional anymore.
};
class B extends React.Component {
static defaultProps = {
name: 'Giulio'
};
props: Props;
render() {
const name: string = this.props.name
return <div>{name}</div>
}
}
But in real usage, a container (A) will use this component (B), and take it's flow type Props as reference, I will add name attribute to it because I declared non-optional. And this doesnt make sense.
@darylszehk01 please reproduce on http://flow.org/try/
I would like to confirm my understanding of this thread, and what was mentioned in https://github.com/facebook/flow/issues/6993:
You can think of the Props type as specifying the interface for
this.props as used inside the component, not the exact props that
need to be provided to construct the component.
And the docs:
You don鈥檛 need to make foo (prop name) nullable (optional
foo?: stringsyntax) in your Props type. Flow will make sure that foo is optional if you have a default prop for foo.
So for a functional component rather than class component:
type Props = {
foo?: string,
}
const Component = ({ foo }: Props) => (
// component using foo
// etc
)
Component.defaultProps = {
foo: "string one",
}
Would have Props properly written as, with no other changes:
type Props = {
foo: string,
}
const Component = ({ foo }: Props) => (
// component using foo
// etc
)
Component.defaultProps = {
foo: "string one",
}
as Flow would make sure that the defaultProps were used when needed?
Which would get around more complex use cases where specifying Props as optional can result in errors such as 'JSX: "Property is missing in undefined" just after checking the object is defined' https://github.com/facebook/flow/issues/6350.
@MaxInMoon Starting from Flow 0.53 just declare your defaultProps the same way as the required ones, without setting them as optional and Flow will do the rest :)
type Props = { foo: string, bar: string, }; class Baz extends React.Component<Props, void> { static defaultProps = { bar: 'whatever', }; }All fine.
@pascalduez all fine? You are completely losing control on required props. In your example bar is not required, since you defined its default value in defaultProps object, but in your type statement you have to set it as required even if it is not. This is really a mess.
Apart from the proposed solution making flowtypes inaccurate, the solution does not work with eslint, as this rule is violated:
Oh man how disappointing. Will be using // $FlowFixMe until this changes.
How could this issue have been closed like this? The proposed solution doesn't work, the linter complains and for a good reason, optional parameters should be declared optional and defaultProps should be handled by flow under the hood, I see maybe this is not a huge priority but still I would like it to be fixed sooner or later.
Most helpful comment
Oh okay I see, thanks. But there is no way for it to work with optionals? I prefer it with optionals because it makes it clear within the context of the component, that those particular props are optional.