Flow Fundamentals For ReactJS Developers
// | |
// MIT License | |
// | |
// Copyright (c) 2018 Ali Sharif | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
// | |
// @flow | |
// Flow Fundamentals For ReactJS Developers | |
/* | |
Tutorial for ReactJS Developers wanting to get started with FlowType. | |
We will go through the basic Flow features to gain a better understanding of how to use FlowType with React. | |
You can uncomment the features one by one and work through this tutorial. | |
If you want to learn the very FlowType basics, refer to "Flow Fundamentals for JavaScript Developers" (https://gist.github.com/busypeoples/61e83a1becc9ee9d498e0db324fc641b) first. | |
If you have any questions or feedback please connect via Twitter: | |
A. Sharif : https://twitter.com/sharifsbeat | |
*/ | |
// Current Version: Flow v0.73 | |
// SETUP | |
/* | |
* Always refer to the official documentation for a deeper understanding. | |
https://flow.org/en/ | |
* Installation and Setup: | |
https://flow.org/en/docs/install/ | |
* Online Try Repl: | |
https://flow.org/try/ | |
* IDE Plugins: | |
Setup Flow for your IDE | |
https://flow.org/en/docs/editors/ | |
*/ | |
// Basics | |
/* | |
To begin with, let's see how we would traditionally work with React and PropTypes. | |
We have a React Class `UserInput` which expects props and manages some local state. | |
Traditionally we would use `PropTypes` to dynmically check if the provided props confirm | |
to an expected shape and type. | |
To prevent any flow errors make sure install flow-typed and then add the prop-type package via flow-typed. | |
For more information check: https://github.com/flowtype/flow-typed | |
*/ | |
import * as React from 'react' | |
// import PropTypes from 'prop-types' // install via flow-typed to remove the flow error! check https://github.com/flowtype/flow-typed | |
// class UserInput extends React.Component<*, *> { | |
// propTypes = { | |
// errors: PropTypes.array, | |
// onSubmit: PropTypes.func.isRequired, | |
// } | |
// state = { | |
// name: '', | |
// email: '', | |
// } | |
// update = event => { | |
// const { name, value } = event.currentTarget | |
// this.setState({ [name]: value }) | |
// } | |
// submit = () => { | |
// const { name, email } = this.state | |
// this.props.onSubmit(name, email) | |
// } | |
// render() { | |
// const { name, email } = this.state | |
// const { errors = [] } = this.props | |
// return ( | |
// <div> | |
// {errors.length > 0 ? ( | |
// <div>{errors.map(error => <div>{error.msg}</div>)}</div> | |
// ) : null} | |
// <input | |
// className="name" | |
// name="name" | |
// value={name} | |
// onChange={this.update} | |
// /> | |
// <input | |
// className="email" | |
// name="email" | |
// value={email} | |
// onChange={this.update} | |
// /> | |
// <button onClick={this.submit}>Sunmit</button> | |
// </div> | |
// ) | |
// } | |
// } | |
/* | |
If you take a closer look at `UserInput`, you will notice that we never defined a state shape, only | |
the expected prop types. Now let's see how we could rewrite the `UserInput` with FlowType. | |
Instead of having to dynamically type check we can now resort to a static check wihtout ever having | |
to run the code. | |
*/ | |
// type PropType = { | |
// errors?: Array<{ msg: string }>, | |
// onSubmit: (name: string, email: string) => void, | |
// } | |
// | |
// type StateType = { | |
// name: string, | |
// email: string, | |
// } | |
// | |
// class UserInputFlow extends React.Component<PropType, StateType> { | |
// state = { | |
// name: '', | |
// email: '', | |
// } | |
// update = event => { | |
// const { name, value } = event.currentTarget | |
// this.setState({ [name]: value }) | |
// } | |
// submit = () => { | |
// const { name, email } = this.state | |
// this.props.onSubmit(name, email) | |
// } | |
// render() { | |
// const { name, email } = this.state | |
// const { errors = [] } = this.props | |
// return ( | |
// <div> | |
// {errors.length > 0 ? ( | |
// <div>{errors.map(error => <div>{error.msg}</div>)}</div> | |
// ) : null} | |
// <input | |
// className="name" | |
// name="name" | |
// value={name} | |
// onChange={this.update} | |
// /> | |
// <input | |
// className="email" | |
// name="email" | |
// value={email} | |
// onChange={this.update} | |
// /> | |
// <button onClick={this.submit}>Sunmit</button> | |
// </div> | |
// ) | |
// } | |
// } | |
/* Using UserIputFlow */ | |
// const renderViewA = () => <UserInputFlow /> // Error! | |
/* | |
You will notice that Flow will complain that property `onSubmit` can not be found. | |
Also interesting to note that we didn't define `errors` either, but `errors` was optional if you recall, so not error here. | |
``` | |
type PropType = { | |
errors?: Array<{ msg: string }>, | |
onSubmit: (name: string, email: string) => void, | |
} | |
``` | |
--------------------------------------------------------------------- | |
Cannot create UserInputFlow element because property onSubmit is missing in | |
props but exists in PropType. | |
--------------------------------------------------------------------- | |
Our next step is to add the defined `onSubmit` function. | |
*/ | |
// const onSubmitFnA = (name: string, email: string, status: string) => { | |
// // do something... | |
// } | |
// const renderViewB = () => <UserInputFlow onSubmit={onSubmitFnA} /> // Error! | |
/* | |
Again, we're greeted with an error here, as we defined `onSubmit` to expect exactly two arguments, both being of string type. | |
*/ | |
// const onSubmitFnB = (name: string, email: string) => { | |
// // do something... | |
// } | |
// | |
// const renderViewC = () => <UserInputFlow onSubmit={onSubmitFnB} /> // Works! | |
/* | |
By passing the correctly defined function via props we finally get rid of the errors. | |
Next you could also add `errors and play around with the `errors` prop if interested. | |
Now let's get back to our Component again and see what we actually defined in more detail. | |
`class yourClass extends React.Component<Props, State>` | |
We can see that React.Component can be parametirized with Prop and State Types. | |
If you don't have any state, you can neglect the State definiton. | |
`class yourClass extends React.Component<Props>` | |
If you don't have any props, you can either use an asterix and neglect being specific or use void. | |
`class yourClass extends React.Component<*, State>` | |
or | |
`class yourClass extends React.Component<void, State>` | |
*/ | |
// class NoStateType extends React.Component<void, *> { | |
// state = { | |
// id: 1, | |
// name: 'foobar', | |
// } | |
// | |
// render() { | |
// return <div>{this.state.id} {this.state.name}</div> | |
// } | |
// } | |
/* | |
You can also define State or Props inline, if you don't want to reuse the type definition. | |
*/ | |
// class InlineStateType extends React.Component<void, {id: number, name: string}> { | |
// state = { | |
// id: 1, | |
// name: 'foobar', | |
// } | |
// | |
// render() { | |
// return <div>{this.state.id} {this.state.name}</div> | |
// } | |
// } | |
/* | |
You can also try to let Flow interfer everything by using `*`: | |
`class yourClass extends React.Component<*, *>` | |
In our `UserIputFlow` Component we defined the `errors` prop to be optional. | |
Let's change that and make it required and add a default error value instead. | |
*/ | |
// type PropTypeNonOptional = { | |
// errors: Array<{ msg: string }>, | |
// onSubmit: (name: string, email: string) => void, | |
// } | |
// | |
// class UserInputFlowWithDefault extends React.Component< | |
// PropTypeNonOptional, | |
// StateType, | |
// > { | |
// state = { | |
// name: '', | |
// email: '', | |
// } | |
// | |
// static defaultProps = { | |
// errors: [], | |
// } | |
// | |
// update = event => { | |
// const { name, value } = event.currentTarget | |
// this.setState({ [name]: value }) | |
// } | |
// submit = () => { | |
// const { name, email } = this.state | |
// this.props.onSubmit(name, email) | |
// } | |
// render() { | |
// const { name, email } = this.state | |
// const { errors = [], onSubmit } = this.props | |
// return ( | |
// <div> | |
// {errors.length > 0 ? ( | |
// <div>{errors.map(error => <div>{error.msg}</div>)}</div> | |
// ) : null} | |
// <input | |
// className="name" | |
// name="name" | |
// value={name} | |
// onChange={this.update} | |
// /> | |
// <input | |
// className="email" | |
// name="email" | |
// value={email} | |
// onChange={this.update} | |
// /> | |
// <button onClick={this.submit}>Sunmit</button> | |
// </div> | |
// ) | |
// } | |
// } | |
// const renderViewD = () => <UserInputFlowWithDefault onSubmit={onSubmitFnB} /> // Works! | |
/* | |
So all we need to do is define defaultProps and FlowType will know that we don't need to define an `errors` prop. | |
*/ | |
/* | |
Typing Stateless Functions Components in React is equivalent to typing functions with Flow in general. | |
For examle, we might want to refactor our input fields and offer an `Input` Component that returns the needed input element. | |
*/ | |
// type InputProps = { | |
// name: string, | |
// value: string, | |
// update: (name: string, email: string) => void | |
// } | |
// | |
// const Input = ({name, value, update} : InputProps) => { | |
// return <input | |
// className={name} | |
// name={name} | |
// value={value} | |
// onChange={update} | |
// /> | |
// } | |
// | |
// <Input name={'email'} value={'foo@bar.baz'} update={(name, value) => {}} /> // works! | |
/* | |
You can also work with default props and Flowtype will not complain if you leave out | |
a needed property. As an exercise try to refactor `Input` to work with default props. | |
*/ | |
/* | |
Let's get back to our `UserInputFlow` Component. What we would also like to do | |
is type our `update` method. | |
Flow offers `SyntheticEvent<T>` specifically for events. As we're using a button | |
to trigger the update. We can very specific by using `SyntheticEvent<HTMLButtonElement>` even. | |
*/ | |
// class UserInputFlowTypedEvent extends React.Component<PropType, StateType> { | |
// state = { | |
// name: '', | |
// email: '', | |
// } | |
// update = (event: SyntheticEvent<HTMLButtonElement>) => { | |
// const { name, value } : HTMLButtonElement = event.currentTarget | |
// this.setState({ [name]: value }) | |
// } | |
// submit = () => { | |
// const { name, email } = this.state | |
// this.props.onSubmit(name, email) | |
// } | |
// render() { | |
// const { name, email } = this.state | |
// const { errors = [], onSubmit } = this.props | |
// return ( | |
// <div> | |
// {errors.length > 0 ? ( | |
// <div>{errors.map(error => <div>{error.msg}</div>)}</div> | |
// ) : null} | |
// <input | |
// className="name" | |
// name="name" | |
// value={name} | |
// onChange={this.update} | |
// /> | |
// <input | |
// className="email" | |
// name="email" | |
// value={email} | |
// onChange={this.update} | |
// /> | |
// <button onClick={this.submit}>Sunmit</button> | |
// </div> | |
// ) | |
// } | |
// } | |
/* | |
For a more detailed introduction into Flow + Events refer to the official | |
documentation: https://flow.org/en/docs/react/events/. | |
*/ | |
/* | |
There are cases where we need to type React.Children, i.e. when using Child Functions | |
or when passing in an array of elements. | |
Basically you can add React.Node for defining the children property, check the following example: | |
*/ | |
// type RowsProps = { | |
// children?: React.Node | |
// } | |
// | |
// const Rows = (props: RowsProps) => ( | |
// <div> | |
// {props.children} | |
// </div> | |
// ) | |
// | |
// const RowsView = () => <Rows>Test</Rows> // Works! | |
/* | |
What if we only want a single child element? | |
*/ | |
// type RowsPropsSingle = { | |
// children: React.Element<any> | |
// } | |
// | |
// const RowSingle = (props: RowsPropsSingle) => ( | |
// <div> | |
// {props.children} | |
// </div> | |
// ) | |
// | |
// const RowSingleViewA = () => <RowSingle /> // Error! | |
// const RowSingleViewB = () => <RowSingle><div>1</div><div>2</div></RowSingle> // Error! | |
// const RowSingleViewC = () => <RowSingle><div>Test</div></RowSingle> // Works! | |
/* | |
What if we expect an array of child elements? | |
*/ | |
// type RowsPropsArray = { | |
// children: React.ChildrenArray<React.Element<any>> | |
// } | |
// | |
// const RowArray = (props: RowsPropsArray) => ( | |
// <div> | |
// {/* ...do something here */} | |
// </div> | |
// ) | |
// | |
// const RowArrayViewA = () => <RowArray /> // Error! | |
// const RowArrayViewB = () => <RowArray>1</RowArray> // Error! | |
// const RowArrayViewC = () => <RowArray><span/><span/><span/></RowArray> // Works! | |
/* | |
What if we want to make sure only strings are allowed as children? | |
*/ | |
// type RowsPropsStringOnly = { | |
// children: React.ChildrenArray<string> | |
// } | |
// | |
// const RowsWithStringOnly = (props: RowsPropsStringOnly) => ( | |
// <div> | |
// {/* ...do something here */} | |
// </div> | |
// ) | |
// | |
// const RowStringsOnylyViewA = () => <RowsWithStringOnly /> // Error! | |
// const RowStringsOnylyViewB = () => <RowsWithStringOnly><span>Test</span></RowsWithStringOnly> // Error! | |
// const RowStringsOnylyViewC = () => <RowsWithStringOnly>Test the example!</RowsWithStringOnly> // Works! | |
/* | |
For a deeper understanding refer to the detailed documentation: | |
https://flow.org/en/docs/react/children/ | |
*/ | |
/* | |
This is it for now. You should have a basic understanding of how to type React Components. | |
To dive deeper into the topic check the official documetation: | |
https://flow.org/en/docs/react/ | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment