A recursive interface for a React Typescript component

Viewed 245

The component is a breadcrumb, it must receive as props an object that contains a title and an url and also an option children node. The maximum depth should be 5, in this case this is a props example:

myProps = {
            title: "level 1",
            url: "/",
            children: {
                        title: "level 2"
                        url: "/level2"
                        children: { 
                                    title: "level 3"
                                    url: "level2/level3"
                                    children: {
                                                title: "level 4"
                                                url: "level2/level3/level4"
                                                children: {
                                                            title: "level 5"
                                                            url: "/level2/level3/level4/level5"
                                                          }
                                               }
                                  }
                       }
          };

But it can also have less nesting, for example:

myProps = {
            title: "title1"
            url: "/"
            children: null
          }

How should the interface look like?

Is this correct?

export interface MyProps {
  title: string;
  url: string;
  children: MyProps;
}
3 Answers

You need to account for those without children, so

export interface MyProps {
  title: string;
  url: string;
  children?: MyProps | null;
}

Your interface is a good start! If I get it right, MyProps.children is optional, right? In that case you can declare it like that:

export interface MyProps {
  title: string;
  url: string;
  children?: MyProps;
}

This allows you to leave out the property children during declaration of a variable of type MyProps and to explicitly assign it undefined:

const myProps: MyProps = {
  title: "title1",
  url: "/"
};

const otherProps: MyProps = {
  title: "title1",
  url: "/",
  children: {
    title: "title2",
    url: "/otherUrl"
  }
};

const yetOtherProps: MyProps = {
  title: "title1",
  url: "/",
  children: undefined
};

If you know upfront the number of nested properties you can use this utility type:

export interface MyProps {
  title: string;
  url: string;
  children: undefined;
}

type Recursive<
  Length extends number,
  Counter extends number[] = [],
  Result = MyProps
  > =
  (Length extends Counter['length']
    ? Result
    : {
      [Prop in keyof MyProps]:
      (Prop extends 'children'
        ? (Length extends [...Counter, 1]['length']
          ? Recursive<Length, [...Counter, 1], undefined>
          : undefined | Recursive<Length, [...Counter, 1], Result>)
        : (Prop extends 'title'
          ? `${string} ${[...Counter, 1]['length'] extends string | number ? [...Counter, 1]['length'] : ''}`
          : MyProps[Prop])
      )
    })

const test0: Recursive<5> = { // ok
  title: `a 1`,
  url: 'string',
  children: {
    title: `a 2`,
    url: ' string',
    children: {
      title: `  3`,
      url: 'string',
      children: {
        title: ` 4`,
        url: 'string',
        children: {
          title: ` 5`,
          url: 'string',
          children: undefined
        }
      },
    },
  },
}

const test1: Recursive<6> = { // ok
  title: `a 1`,
  url: 'string',
  children: {
    title: `a 2`,
    url: ' string',
    children: {
      title: `  3`,
      url: 'string',
      children: {
        title: ` 4`,
        url: 'string',
        children: {
          title: ` 5`,
          url: 'string',
          children: {
            title: ` 6`,
            url: 'string',
            children: undefined
          },
        },
      },
    },
  },
}

const test2: Recursive<5> = {
  title: `a 1`,
  url: 'string',
  children: {
    title: `a 2`,
    url: ' string',
    children: {
      title: `  3`,
      url: 'string',
      children: {
        title: ` 4`,
        url: 'string',
        children: {
          title: ` 5`,
          url: 'string',
          children: {
            title: ` 6`,
            url: 'string',
            children: undefined
          }
        },
      },
    },
  },
}

Playground

Explanation

Recursive parameters: Length - the number of nested properties Counter - since it is impossible to make math operations in TS I'm using a tuple and increase the length every time by 1 [...Counter, 1] Result - straightforward - result :D

How does it work?

Each step starts with comparison of Length and Counter['length']. If Counter['length'] is equal desired number of nested properties - return Result.

Otherwise, iterate through each property of MyProps end each time when loop encounter children property - call it recursively with increased Counter.

Recursive<5> - means that you are allowed to declare up to 5 nested children. You can declare less but no more

I have added an extra logic for title property. Each title property has its own number.

That's all.

P.S. Here, in my blog, you can find more explanation

Related