Home > Software engineering >  Properly narrowing down accessor type in Solid.js
Properly narrowing down accessor type in Solid.js

Time:05-24

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

CodePudding user response:

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

CodePudding user response:

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>
  • Related