Search code examples
reactjstypescriptcomponentscreate-react-appreact-tsx

TSX: Nested recursive Array of objects interface


I can't get my typescript interface to work with a react tsx component. I've got all the items inside an array of objects with possible sub items.

I've tried lots of different interfaces but always ended up with some kind of error, currently only the map function seems to not work with the interface. (I'm a noob and started using typescript yesterday for the first time :P)

enter image description here

interface NavListItem {
  ID: string;
  title: string;
  path_url: string;
  child_items?: NavListProps;
}

interface NavListProps {
  items: { [index: number]: NavListItem };
}

const NavList: React.FC<NavListProps> = ({ items }) => {
  return items.map(item => (
    <li key={item.ID}>
      <Link to={item.path_url}>{item.title}</Link>
      {item.child_items ? (
        <ul>{<NavList items={item.child_items} />}</ul>
      ) : null}
    </li>
  ));
};

My goal is to have no errors and a working component. I appreciate all the help!


Solution

  • If items is supposed to be an array, then the correct interface for the props is this:

    interface NavListProps {
      items: NavListItem[];
    }
    

    As you've defined it, items is an object with arbitrary key value pairs, who's only restriction is that the keys must be numbers, and the values must be NavListItems. This doesn't mean it's an array though, so typescript is pointing out that that type does not have a map method on it.

    A couple of additional issues (as discussed in the comments):

    1) When you recursively render NavList, you're passing in item.child_items, but that's defined to be a single NavListProps, when instead it should be a NavListItem[]:

    interface NavListItem {
      ID: string;
      title: string;
      path_url: string;
      child_items?: NavListItem[];
    }
    

    2) Your component is returning just an array, but this should instead be wrapped in a Fragment:

    const NavList: React.FC<NavListProps> = ({ items }) => {
      return (
        <React.Fragment>
          {items.map(item => (
            <li key={item.ID}>
              <Link to={item.path_url}>{item.title}</Link>
              {item.child_items ? (
                <ul>{<NavList items={item.child_items} />}</ul>
              ) : null}
            </li>
          }
        </React.Fragment>
      ));
    };
    

    P.S.: if your build is set up correctly, it's possible to do <></> as shorthand for <React.Fragment></React.Fragment>