javascriptflowtypenullablerefinement-type

flowtype nullable object immutable property refinement


I want to use refinement for a property that can be null. This object with the checked property then passed to function as an argument.

/* @flow */
const a: {+foo: ?string} = {};

const fun = (obj: {+foo: string}) => {
  return obj
}

if (a.foo) {
  fun(a) // null or undefined [1] is incompatible with string
}

Try flow

It shouldn't work with an object with mutable property, because this property can be changed to null later. So that's why I use the immutable property. But it still doesn't work.

Is there a way to pass object with the refined property?


Solution

  • Refining a property of an object refines the property, not the object.

    // `a.foo` is of type `?string`
    // `a` is of type `{+foo: ?string}`
    if (a.foo) {
      // within this block, `a.foo` is of type `string` (no `?`)
      // `a` is of type `{+foo: ?string}`
    }
    // `a.foo` is of type `?string`
    // `a` is of type `{+foo: ?string}`
    

    In this particular case, I would probably do something like this:

    if (a.foo) {
      fun({ foo: a.foo });
    }
    

    (Try)

    Just because it's such a simple case. In a more complex case, you'd want to use disjoint unions.

    type A = {
      +foo: string,
    };
    
    type B = {
      +foo: void,
    };
    
    type T = A | B;
    
    const a: T = ({ foo: undefined }: B);
    
    const fun = (obj: A) => {
      return obj
    }
    
    // `a` is of type `A | B` (which is type `T`)
    if (a.foo) {
      // inside this block `a` is of type `A`
      fun(a);
    }
    // `a` is of type `A | B` (which is type `T`)
    

    (Try)

    At the end of the day, there is no super direct way to convert { +foo: ?string } to { +foo: string } because they are two completely different complex types, and must be handled as such.