1

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?

iofjuupasli
  • 3,818
  • 1
  • 16
  • 17
  • 1
    This seems like a bug to me since you can refine `a.foo: string` in the conditional and, as you said, the property is read only--I'd suggest opening an issue with the Flow project. – user11307804 Sep 19 '19 at 14:06

2 Answers2

3

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.

Lyle Underwood
  • 1,304
  • 7
  • 12
1

You could try using indirect casting, where you cast the type onto any because any accepts any type of value as an input. Then because the any type also allows you to read all possible types from it, you can assign the any value to your type because an any is also your type.

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

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

if (a.foo) {
  fun((a: any))
}
Krzysztof Krzeszewski
  • 5,912
  • 2
  • 17
  • 30
  • It's an option, thanks. It can be reduced also to `fun((a: any))`. However, it seems like a dirty hack – iofjuupasli Sep 19 '19 at 13:16
  • i don't think there is any non "dirty" way to do it, other than changing the type of the function argument to accept null & undefined or making `foo` writable and assigning something like an empty string in case of null – Krzysztof Krzeszewski Sep 19 '19 at 13:29