Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.