typescriptsveltesvelte-componenttanstackreact-query

How to narrow types with Tanstack Svelte query?


Problem

I'm having trouble narrowing types with Tanstack Svelte Query. I am migrating my app from React to Svelte.

My query queryGetMyPageCollection() returns either an object PageCollection or false. PageCollection has a getPageByPath() method that returns a Page object or undefined.

This is where the problem occurs:

        {#if $query.data && $query.data.getPageByPath(data.slug)}
            <p>Hooray! We found your page!</p>
            <DisplayPage page={$query.data.getPageByPath(data.slug)} />

I am checking $query.data, which is false or the PageCollection object, and, if the object exists, calling getPageByPath() against the query string to see if we can load a Page object. If the page object exists, then I display the page in the <DisplayPage> component.

This code works but TypeScript complains about the <DisplayPage> line:

Error: Type 'Page | undefined' is not assignable to type 'Page'.
Type 'undefined' is not assignable to type 'Page'. (ts)

However, I know that the getPageByPath() call for the <DisplayPage> component will not be undefined because I checked it in the #if statement.

Similar code works in React, so I'm guessing that in Svelte, TypeScript is not setting the type for the method call in the <DisplayPage> component based on the #if statement directly above it.

Question

How do I code this so that TypeScript does not produce an error? Please do not answer "set @tsignore"; I want to know how to actually solve this the TypeScript/Svelte way.

Full component code

<script lang="ts">
  export let data;
  const query = createQuery({
        queryKey: queryKeyUsePages,
        queryFn: async () => queryGetMyPageCollection()
    });
</script>


<div>
    {#if $query.isLoading}
        <p>Loading...</p>
    {:else if $query.isError}
        <p>Error: {$query.error.message}</p>
    {:else if $query.isSuccess}
        <h1>Here is the page</h1>
        {#if $query.data && $query.data.getPageByPath(data.slug)}
            <p>Hooray! We found your page!</p>
            <DisplayPage page={$query.data.getPageByPath(data.slug)} />
        {:else}
            <p>On no! We didn't find your page.</p>
            {#if $query.data}
                <p>Available paths:</p>
                <ul>
                    {#each $query.data.pages as page}
                        <li><a href="/{page.path}">{page.title}</a></li>
                    {/each}
                </ul>
            {/if}
        {/if}
    {/if}
</div>

Solution

  • That should not work in React either, you need to store the result of the function call, otherwise the type will not be narrowed.

    E.g. using @const:

    {:else if $query.isSuccess}
      <h1>Here is the page</h1>
      {@const page = $query.data && $query.data.getPageByPath(data.slug)}
      {#if page}
        <p>Hooray! We found your page!</p>
        <DisplayPage {page} />
      {:else}
        ...