4

I have a Solid.js code that looks like this

import { render } from "solid-js/web";
import { createSignal , Component } from "solid-js";

const Number: Component<{value: number}> = ({value}) => <b>{value}</b> 

const App: Component = () => {
  const [foo] = createSignal<number | null>(null);

  return (
    foo() 
      ? <Number value={foo()} /> /* foo() is number | null, causing an error */
      : <div>foo is null</div>
  );
}

render(() => <App />, document.getElementById("app")!);

How can I properly narrow down type of foo() accessor so I can safely pass it as Number's props?

On a normal variable, the ternary operator narrows down the type properly:

let bar!: number | null;

bar 
    ? <Number value={bar} /> // bar's type is narrowed from number | null to number
    : <div>bar is null</div>

But it doesn't seem to work with accessor variable

Playground

Owl
  • 6,337
  • 3
  • 16
  • 30
  • 2
    Try using show component https://playground.solidjs.com/?version=1.4.1#NobwRAdghgtgpmAXGGUCWEwBowBcCeADgsrgM4Ae2YZA9gK4BOAxiWGjIbY7gAQi9GcCABM4jXgF9eAM0a0YvADo1aAGzQiAtACsyAegDucAEYqA3EogcuPfr2ZCouOAGU0Ac2hreWXgGEFLghhXD9XAAtaQylZeUUVOg1tPQsrK2ZaCDI+ADl6GBNxRACgrNCAHhAANyg1ejgSiAKixkkAPl4AXl4AChq6hskASm7OipN2gfq4SQr9SeUIDKyc3gBBQkISwM5yiD4e3tGuzpArXgdVvmAZWloAXW6HJxd3LzqK5sLxXgAfXjNNRqdq9IFqYaWZYQS5CXBMGG9C6XXgVSLRXiGCLCLogO60Y7SGR1NQmKDMADWuIqIjQ1Xa+N4aDIgPowPmtPpHWRlxAvRkJ3G+R+ElqM1xMmk+nakh5qP06MM7WRkKssuhQlE4l6xzGqM2hF40r8IlozAKoQAdB44LgAKJqODwA4AIXwAEkREiwFAtiphgBCSFgSQPIA – Neeraj May 23 '22 at 04:40
  • Ah forgot that `Show` exists ‍♂️. Would you like to put your answer below so I can mark it? – Owl May 23 '22 at 04:53

2 Answers2

2

You can use Show component to narrow down types

import { render } from "solid-js/web";
import { createSignal, Component, Show } from "solid-js";

const Number: Component<{value: number}> = ({value}) => <b>{value}</b> 

const App: Component = () => {
  const [foo] = createSignal<number | null>(null);

  return (
    <Show when={foo()} fallback={<div>foo is null</div>}>
      {(f) => <Number value={f} />}
    </Show>
  );
}

render(() => <App />, document.getElementById("app")!);

Playground

Owl
  • 6,337
  • 3
  • 16
  • 30
Neeraj
  • 174
  • 4
0

Narrowing down types and rendering values are two different concept. The condition you are using in your ternary is not specific enough to eliminate non-number values. If you use a right conditional, typeof val() === 'number', typescript will figure the type correctly.

const Num: Component<{ value: number }> = (props) => {
  return <div>{props.value}</div>
}

const App = () => {
  const [val] = createSignal<number | null>(null);

  return (
    <div>
      {typeof val() === 'number' ? <Num value={val()} /> : <div>{val()}</div>}
    </div>
  );
};

Otherwise, the most straightforward way to narrow down a type is casting, val() as number, but it will suppress the type giving you false positives. You should you use casting when you know the type but typescript can not figure it out.

The Show component in the accepted answer does not narrow down types but coerces the when value into a boolean and renders provided elements accordingly. If you check its type its is Show<T> which means any. What you are looking for is conditional rendering based on the type of the value.

Solid uses JSX for its UI layer so you can render items conditionally using an expression, the way described in JSX specification: Whatever you return from the expression between curly brackets {} will be printed on the screen, unless it is a falsy value.

You can use && operator to conditionally render an element/component:

const App = () => {
  const myVal = 'two';
  return (
    <div>
      {myVal === 'one' && <div>One</div>}
      {myVal === 'two' && <div>One</div>}
      {myVal === 'three' && <div>One</div>}
    </div>
  );
};

You can use a ternary operator to decide between two elements/components:

const App = () => {
  const isLoggedIn = true;
  return (
    <div>
      {isLoggedIn ? (
        <Logout onClick={handleLogout} />
      ) : (
        <Login onClick={handleLogin} />
      )}
    </div>
  );
};

So, in most basic way, you can use typeof operator to filter numbers only values:

const App = () => {
  const [val] = createSignal<number | null>(0);

  return (
    <div>
      {typeof val() === 'number' ? <div>number: {val()}</div> : <div>Not a number</div>}
    </div>
  );
};

Solid provides built-in Show component for rendering elements based on two mutually exclusive conditions, like we do with ternary operator. The result will be memoized to reduce downstream rendering.

<Show when={true} fallback={<Fallback />}>
  <div>My Content</div>
</Show>

It also takes a render function:

<Show when={state.count} fallback={<div>Loading...</div>}>
  {(props) => <div>{props.count}</div>}
</Show>

Here how you can do it with Show component:

const App = () => {
  const [val] = createSignal<number | null>(null);

  return (
    <Show when={typeof val() === 'number'} fallback={<div>Not a number</div>}>
      <div>number: {val()}</div>
    </Show>
  );
};

There is also <Switch>/<Match> to render elements based on custom conditions:

<Switch fallback={<div>Aww Week Day!</div>}>
  <Match when={state.day === "saturday"}>
    <Saturday />
  </Match>
  <Match when={state.day === "sundat"}>
    <Sunday />
  </Match>
</Switch>
snnsnn
  • 10,486
  • 4
  • 39
  • 44
  • I don't think this is relevant with my question at all. The issue I'm having is the accessor's (`foo()`) type can't be narrowed from `number | null` to `number` using the ternary operator on the JSX, this is more like a TypeScript specific question. Besides that, your edit to my question caused the title of my question to be different from what I intended to ask. – Owl May 23 '22 at 12:03
  • @Owl I thought that way but accepted answer clearly shows conditional rendering, not narrowing down. Should we fixed the question or the answer or both? – snnsnn May 23 '22 at 12:13
  • The accepted answer shows conditional rendering, but it also solves my issue. By using `Show` component, the render function parameter type is narrowed properly from `number | null` to `number`, therefore solving my issue. Check my latest Playground link included on my question for an example. – Owl May 23 '22 at 12:18
  • @Owl It is not. If you look at the type of Show you see it is `Show` which means you are passing number | null. So, there is no narrowing at all. It prints any truthy value because of automatic coercion, not because of type narrowing. Try passing a 0, it will print fallback component. Either way it is not type narrowing. – snnsnn May 23 '22 at 12:24
  • You are right, I tried `` on my code and the render function parameter type is converted to `true` . So I guess the question remains unanswered, unless if you have a way to narrow down accessor type. But my gut feeling tells me that this is a limitation from TypeScript it self. – Owl May 23 '22 at 12:30
  • @Owl I reverted the question back but in its current form it will be confusing for other people. If you don't mind let me edit back. – snnsnn May 23 '22 at 12:31
  • As long as the question's point is "How to narrow down accessor's type (TypeScript's type) in JSX template", not "How to do conditional rendering" :) – Owl May 23 '22 at 12:34
  • Improved the answer a bit to address your problem. Hope it helps. – snnsnn May 23 '22 at 13:02