Search code examples
reactjstypescripttranslationtype-inference

Generic tree with UNIQUE generic nodes


Problem description

I have a generic tree with generic nodes. You can think about it like it is a extended router config with multi-level children elements.

The catch is, that each node can have other generic type that its parent (more details - Typescript Playground).

So when node has children, the problem is lying in typing its nodes generics.

Code

type ReactNode = string



type languageTag = 'en' | 'ru'/* | 'de' | 'cs' | 'fr' | 'cn' | 'pl' */



// the type below may be the problem
type translations<T extends unknown = unknown> = {
  [key in languageTag]: T
}



interface pageData<T extends unknown = unknown> {
  path: string,
  contentTranslations: translations<T>
  element: ReactNode,
  children?: Array<pageData<T>>
}



const informationPageData: pageData<{ hobbies: string/* , fullName: string, birthDate: string, sex: string, ... */ }> = {
  path: 'information',
  contentTranslations: {
    en: { hobbies: 'hobbies' },
    ru: { hobbies: 'хобби' }
  },
  element: 'profile information'
}



const profilePageData: pageData<{ recentPosts: string }> = {
  path: ':profileId',
  contentTranslations: {
    en: { recentPosts: 'recent posts' },
    ru: { recentPosts: 'недавние посты' }
  },
  element: 'profile component',
  children: [ informationPageData ] // Error
}



const profilesPageData: pageData<{ youMayKnow: string }> = {
  path: '/profiles',
  contentTranslations: {
    en: { youMayKnow: 'you may know' },
    ru: { youMayKnow: 'возможно вы знакомы' }
  },
  element: 'profiles list',
  children: [ profilePageData ] // Error
}

Questions

Where should I get those types from?

The goal is to make it as much DRY as it could be. Maybe with type inferring?

Typescript Playground

Can I have your help please?


Solution

  • Your problem with pageData interface is the parent T is the same type required by the children. What you want is to open up the generic type to accommodate any record therefor allowing the children to define their own properties.

    interface pageData<T extends Record<string, string>> {
      path: string,
      contentTranslations: translations<T>
      element: ReactNode,
      children?: Array<pageData<Record<string, string>>>
    }
    

    You will still get the strongly typed nature of the component props, but without the inherited restrictions