Home > Software engineering >  A component that prevents the usual existence check or None, but how to avoid this repetition?
A component that prevents the usual existence check or None, but how to avoid this repetition?

Time:01-16

Many times in my components/pages I need to use this pattern:

{#if player.name}
  {player.name}
{:else}
  None
{/if}

So I created a new component called MaybeNone:

<script lang="ts">
    export let condition = false;
</script>

{#if !condition}
    None
{:else}
    <slot />
{/if}

to use like this:

<MaybeNone condition={!!player.name}>
  Name is: {player.name}
</MaybeNone>

but sometimes I need to use methods like:

{#if player.name}
  {calculate_player_name(player)}
{:else}
  None
{/if}

with:

export const calculate_player_name = (
    player: Pick<Player, 'name' | 'nickname'>
): string => {
    // calculate result...

    return result;
};

player?: {
    name?: string | null;
    nickname?: string | null;
    team?: string | null;
    id: string;
    createdAt: Date;
  } | null;
}>;

And now the usage of the MaybeNone component is not good:

<MaybeNone condition={!!player.name}>
  <!-- There is an error here because I need to check player.name before calling `calculate_player_name()` obviously -->
  {calculate_player_name(player)}
</MaybeNone>

and I'm forced to use instead:

<MaybeNone condition={!!player.name}>
  {#if player.name}
    {calculate_player_name(player)}
  {/if}
</MaybeNone>

because the Typescript error is of course:

Type 'undefined' is not assignable to type 'Pick<Player, "name" | "nickname">'.ts(2345)

which makes the usage of MaybeNone useless or not so good as I thought.

Can you suggest an alternative? A fix?

Thanks for your amazing help!

CodePudding user response:

Would approach this via a generic component that has slot properties, something like this:

<script lang="ts">
  type T = $$Generic;
  export let maybe: T | null | undefined;

  // Workaround for type not being narrowed correctly in parent
  $: notNull = maybe as T; 
</script>

{#if maybe}
  <slot value={notNull} />
{:else}
  None
{/if}

Which then can be used along the lines of:

<MaybeNone maybe={player} let:value>
  {calculate_player_name(value)}
</MaybeNone>

This should work according to the types of the function, but they probably are inaccurate if you need to check that name is defined as well. Making sure that this is the case would be a bit more verbose:

<MaybeNone maybe={player?.name == null ? null : player} let:value>
  {calculate_player_name(value)}
</MaybeNone>

(If player itself can be undefined/null, there really should be a ?. on property access. TS's strict null checks will cause errors otherwise.)

  • Related