Home > front end >  How to iterate over solid-js children with typescript annotations
How to iterate over solid-js children with typescript annotations

Time:01-18

Say I have the following component, how would I iterate over and render each child to do stuff like wrapping each child with other components?

interface StuffProps {
  children: `?`
}

function Stuff({ children }: StuffProps) {
  // ?
}

I've tried setting children: JSX.Element and then doing <For each={children}> ... </For> but it gives me a typescript error.

CodePudding user response:

Not sure if this is right, but I may have found an answer using the children helper function provided by solid-js.

My solution looks something like this:

import { JSX, children as useChildren } from 'solid-js';

interface StuffProps {
  children: JSX.Element
}

function Stuff({ children }: StuffProps) {
  const c = useChildren(() => children).toArray()
  <For each={c}> ... </For>
}

CodePudding user response:

If you are not going to use children in some effects, you can wrap children with some JSX element directly:

import { Component, JSXElement } from 'solid-js';
import { render } from 'solid-js/web';

const Stuff: Component<{ children: JSXElement }> = (props) => {
  return (
    <div><span>Wrapper:{props.children}</span></div>
  )
};

const App = () => {
  return (
    <div>
      <Stuff>
        <h1>Some Title</h1>
      </Stuff>

      <Stuff>
        <h1>Other Title</h1>
        <p>Some paragraph</p>
      </Stuff>
    </div>
  );
}

render(() => <App />, document.body);

Here we avoid destructuring props intentionally because it will remove reactivity since you will be assigning them into some local variable. Reactivity is transmitted between components through function calls. But I will ignore this rule for now for clarity.

If you are going to use props.children withFor components you will get error because children is not guaranteed to be an array since JSX element can be any of number | boolean | Node | JSX.ArrayElement | JSX.FunctionElement | (string & {}) | null | undefined.

So, you need to make sure children is an array:

import { render, } from 'solid-js/web';
import { Component, JSXElement } from 'solid-js';

const Stuff: Component<{ children: JSXElement }> = (props) => {
  const children = Array.isArray(props.children) ? props.children : [props.children];
  return (
    <ul>
      <li>{children.map(child => child)}</li>
    </ul>
  )
};

const App = () => {
  return (
    <div>
      <Stuff>
        <h1>Some Title</h1>
      </Stuff>

      <Stuff>
        <h1>Other Title</h1>
        <p>Some paragraph</p>
      </Stuff>
    </div>
  );
}

render(() => <App />, document.body);

Now you can use it with For component:

import { render, } from 'solid-js/web';
import { Component, For, JSXElement } from 'solid-js';

const Stuff: Component<{ children: JSXElement }> = (props) => {
  const children = Array.isArray(props.children) ? props.children : [props.children];
  return (
    <ul>
      <For each={children}>
        {item => <li>{item}</li>}
      </For>
    </ul>
  )
};

const App = () => {
  return (
    <div>
      <Stuff>
        <h1>Some Title</h1>
      </Stuff>

      <Stuff>
        <h1>Other Title</h1>
        <p>Some paragraph</p>
      </Stuff>
    </div>
  );
}

render(() => <App />, document.body);

Solid provides this convenience through child function from the main library but you will still get type error if you use them with For component. That is because the result can be false | readonly unknown[] | null | undefined but this can be fixed by using .toArray method instead of function invocation:

import { render, } from 'solid-js/web';
import { children, Component, For, JSXElement } from 'solid-js';

const Stuff: Component<{ children: JSXElement }> = (props) => {
  const resolved = children(() => props.children);

  return (
    <ul>
      <For each={resolved.toArray()}>
        {item => <li>{item}</li>}
      </For>
    </ul>
  )
};

const App = () => {
  return (
    <div>
      <Stuff>
        <h1>Some Title</h1>
      </Stuff>

      <Stuff>
        <h1>Other Title</h1>
        <p>Some paragraph</p>
      </Stuff>
    </div>
  );
}

render(() => <App />, document.body);

As I said, this is a convenience for accessing children inside effects. Extracting them only to wrap in another element does not much make sense since you can do that directly like because expressions are valid JSX elements:

<div>{props.children}</div>
  • Related