简体   繁体   中英

How to iterate over solid-js children with typescript annotations

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.

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>
}

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 with For 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 false | readonly unknown[] | null | undefined 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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM