flowtypeflow-typed

Cannot return object literal because null or undefined - Flow


Mixing the mayBe types with Optional props of an object literal

 type Response = {
        data?: string;
    }

 function length(): ?string {
      return undefined;
 }

 function test(): Response {
    const data = length()
    return {
        data
    }
 }

12:         data        ^ Cannot return object literal because null or undefined [1] is incompatible with string [2] in property `data`.
References:
5: function length(): ?string {
                      ^ [1]
2:     data?: string;
              ^ [2]

Test Flow

Following the flow documentation for MayBe types and Object Types I am not sure why I get the error, is there a way to fix this ?


Solution

  • Maybe types and optional object properties are not quite the same thing in flow, and are not always compatible.

    First let's look at your doc link for Maybe Types:

    Maybe types accept the provided type as well as null or undefined. So ?number would mean number, null, or undefined.

    So ?number is basically number | null | undefined.

    Now let's look at your Object Types link from the docs:

    In addition to their set value type, these optional properties can either be void or omitted altogether. However, they cannot be null.

    So if we do type Response { data?: string } then response.data is basically string | void. Note that void is a totally separate type from null.

    So now let's break down your example:

    type Response = {
      data?: string; // void | string
    }
    
    function length(): ?string { // void | string | null
      return undefined;
    }
    
    function test(): Response {
      const data = length()
      // at this point, as far as flow knows, `data` can be `void`, `string`, or
      // `null`, and we're trying to set it as the `data` property on our
      // `Response` type which expects a `void` or a `string`, and does not
      // expect a `null`.
      return {
        data
      }
    }
    

    So basically, Response.data is expecting void | string and you're trying to set it with a void | string | null. A type must change somewhere in order for you to complete the operation successfully. There are multiple possible approaches:

    Option one, change length return value to be more specific. Probably the simplest:

    function length(): void | string {
      return undefined;
    }
    

    We've removed the possibility of data being null, so no more type error.

    Option two, make Response.data a maybe type

    If they're both maybe types, the problem goes away. The smallest possible change would be this one:

    type Response = {
        data?: string | null;
    }
    

    We've just added the possibility of Response.data being null. Now it can accept the return type of length, types match, no error. But this is a bit confusing, combining the null with the optional property. We could just do this instead:

    type Response = {
        data: ?string,
    }
    

    Option three, refine the type:

    function test(): Response {
      const data = length()
      // at this point response.data is `void`
      const response = {};
      if (data) {
        // data is now a `number`, not `number | void | null`
        response.data = data;
      }
      // response.data is now `number | void` (not `null`)
    
      return response
    }
    

    The decision of which of these options to use depends almost entirely on the best API for whatever the problem at hand is, and also possibly stylistic choice on how to approach optional vs maybe types in general.