Checking React and Redux code with Flow

JSUnconf 2016

Oliver Zeigermann / @DJCordhose

http://djcordhose.github.io/react-intro-live-coding/2016_jsunconf.html

Flow is a static type checker, designed to quickly find errors in JavaScript applications

Flow

soundness, no runtime exceptions as goal

  • http://flowtype.org/
  • By Facebook
  • Flow is a static type checker, designed to quickly find errors in JavaScript applications
  • Not a compiler, but checker
  • Works without any type annotations
  • Very good at inferring types
  • If present, type annotations can very easily be removed by babel for runtime

Basics


let obj: string;
obj = 'yo';
// Error: number: This type is incompatible with string
obj = 10;


function sayIt(what: string) {
    return `Saying: ${what}`;
}
const said: string = sayIt(obj);


class Sayer {
    what: string;

    constructor(what: string) {
        this.what = what;
    }

    sayIt(): string {
        return `Saying: ${this.what}`;
    }
}

React Example #1: Simple Check with Type Inference

Good


const element = <HelloMessage greeting="Hello"/>;
ReactDOM.render(element, mountNode);
            

Bad


// ERROR: Must be a ReactElement
const element = 'just a string'; // type is interred as string
ReactDOM.render(element, mountNode);
            

Generics

aka Parametric Types

Consider this type hierarchy


class Animal {
   name: string;
   constructor(name: string) {
       this.name = name;
   }
}

class Dog extends Animal {
    // just to make this different from cat
    goodBoyFactor: number;
}


class Cat extends Animal {
    purrFactor: number;
}

Generic Type information

Types can be parameterized by others

Most common with collection types


let cats: Array<Cat> = []; // can only contain cats
let animals: Array<Animal> = []; // can only contain animals


// nope, no cat
cats.push(10);
           

// nope, no cat
cats.push(new Animal('Fido'));
           

// cool, is a cat
cats.push(new Cat('Purry'));
           

// cool, cat is a sub type of animal
animals.push(new Cat('Purry'));
           

React #2: Type Declarations


type Props = {
    // ERROR: trying to assign this boolean to state
    // greeting: boolean
    greeting: string
};
        

type State =  {
    greeting: string;
};
        

class HelloMessage extends React.Component<any, Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = {greeting: this.props.greeting};
    }
}
        

PropTypes on Steroids

Non-Nullable Types


function foo(num: number) {
    if (num > 10) {
        return 'cool';
    }
}

// error: call of method `toString`.
// Method cannot be called on possibly null value
console.log(foo(100).toString());

Flow does catch this

But why?

Flow does not infer string as the return type

The inferred type is something else


               // error: return undefined. This type is incompatible with string
function foo(num: number): string {
	if (num > 10) {
		return 'cool';
	}
}


// nullable type: the one inferred
function foo(num: number): ?string {
	if (num > 10) {
		return 'cool';
	}
}


// to fix this, we need to check the result
const fooed: ?string = foo(100);
if (fooed) {
    fooed.toString();
}

Non-nullable types

Types are non-nullable by default in Flow

Nullable types in Flow also possible

React Example #3: Redux Action types


type Action<ActionType, PayloadType> = {
    type: ActionType;
    payload: PayloadType;
}
            

// Action is a generic type
type GreetingAction =
    Action<'UPDATE_GREETING' | 'RESET_GREETING', ?string>;
            

React Example #3: Redux Action Creators


function updateGreeting(greeting: string): GreetingAction {
    return {
        // must be UPDATE_GREETING' or 'RESET_GREETING
        type: 'UPDATE_GREETING',
        // must be a string or null/undefined
        payload: greeting
    };
}
            

function resetGreeting(): GreetingAction {
    return {
        type: 'RESET_GREETING',
        // payload may be null or undefined
        payload: null
    };
}
            

React Example #3: Redux Reducer


// ERROR: this for sure is string
// function greetingReducer(state = 'Hello', action): number {
function greetingReducer(state: string = 'Hello',
                         action: GreetingAction): ?string {
    // flow will likely check that all cases are matched in the future
    // https://github.com/facebook/flow/issues/1473
    switch (action.type) {
        case 'UPDATE_GREETING':
            return action.payload;
        case  'RESET_GREETING':
            return '';
        default:
            return state;
    }
}
            

Changes the way you write code

Allows for reliable refactoring (at least in WebStorm)

Catches errors before you even start your app

Tooling

  • type annotations can be removed by babel
  • WebStorm supports flow
  • npm flow-bin

Thank you!

Questions / Discussion

Oliver Zeigermann / @DJCordhose