Question: How to use Flow types with React?

Question

How to use Flow types with React?

Answers 1
Added at 2016-12-26 21:12
Tags
Question

Let's say I have an App component whose state consists of an object whose shape is given by the following Flow type:

type Person = {
    fname: string,
    lname: string
};

To assert that the state of my component is indeed of type Person I create an artificial local variable inside getInitialState so that I can annotate the type:

const App = React.createClass({
    getInitialState: function() {
        const person : Person = {fname: 'John', lname: 'Doe'};
        return {person: person};
    }
    , render: function() {
        return (
                <PersonDetails person={this.state.person}/>
        );
    }
});

The above works (even though having to declare the const person is a minor inconvenience). The problem arises when I wish to pass that state as the property of another component. In that case, I have found no other solution but to re-define the shape of Person using React's PropTypes API:

const PersonDetails = React.createClass({
    propTypes: {
        person: React.PropTypes.shape({fname: React.PropTypes.string.isRequired,
                                       lname: React.PropTypes.string.isRequired}).isRequired
    },
    render: function() {
        return (
                <div>
                <span>{this.props.person.fname}</span>
                <span>{this.props.person.lname}</span>                
                </div>
        );
    }
});

This is clearly not DRY plus I am using two different methods to provide static type information (Flow and the React.PropTypes API).

Is there a way to allow the PersonDetails component to reuse the type declaration of Person?

The only way I have found is using proper classes (not React types):

class Person {
    fname: string;
    lname: string;
    constructor(fname: string, lname: string) {
        this.fname = fname;
        this.lname = lname;
    }
};

const PersonDetails = React.createClass({
    propTypes: {
        person: React.PropTypes.instanceOf(Person).isRequired

    },
    render: function() {
        return (
                <div>
                <span>{this.props.person.fname}</span>
                <span>{this.props.person.lname}</span>                
                </div>
        );
    }
});

const App = React.createClass({
    getInitialState: function() {
        const person : Person = new Person('John', 'Doe');
        return {person: person};
    }
    , render: function() {
        return (
                <PersonDetails person={this.state.person}/>
        );
    }
});

export default App;

… while the above succeeds in re-using the definition it is not clear to me when I ought to use Flow types versus classes to statically type-check my components. Plus, I am not sure the two methods are really equivalent (e.g. in terms of being able to express null-ability of values).

Answers
nr: #1 dodano: 2016-12-26 21:12

Declare your type once and export it:

// types/index.js

export type Person = {|
  fname: string,
  lname: string,
|}

Then reuse it everywhere.

In component's state:

// App.js

import type { Person } from './types'

type State = {|
  person: Person,
|}

class App extends Component {
  state: State = {
    person: { fname: 'John', lname: 'Doe' },
  }

  render() {
    return (
      <PersonDetails person={this.state.person} />
    )
  }
}

In component's props:

// components/PersonDetails.js

import type { Person } from '../types'

type Props = {|
  person: Person,
|}

// stateful syntax
class PersonDetails extends Component {
  props: Props

  render() {
    const { person } = this.props
    return (
      <div>
        <span>{person.fname}</span>
        <span>{person.lname}</span>               
      </div>
    )
  }
}

// stateless syntax
const PersonDetails = ({ person }: Props) => (
  <div>
    <span>{person.fname}</span>
    <span>{person.lname}</span>               
  </div>
)
Source Show
◀ Wstecz