Using multiple mapped types for objects key/values. Need only one value from union?

Viewed 800

Having some trouble trying to get the type to work correctly here.

I want to specify in an object that each top-level key is one of the strings from a union, and the value is a nested object that has keys of one of the strings from another union, and its value can be the string from the first union (kind of like a state-machine).

Eg:

type Status = 'idle' | 'loading' | 'error' | 'view'
type Actions = 'search' | 'search_success' | 'search_failure'

const app = {
  idle: {
    search: 'loading'
  },
  loading: {
    search_success: 'view',
    search_fail: 'error',
  },
  error: {
    search: 'loading'
  }
  view: {
    search: 'loading'
  }
}

My attempts to type this have been:

type States = { [key in Status]: { [key in Actions]: Status } };

Though this doesn't work. Could somebody explain how I could make use of some utility types possibly to allow the nested object key to not require ALL values from the Actions type?

Thanks!

2 Answers

I simplified your code a bit.

This definition

type Status = 'idle' | 'loading'
type State = { [key in Status]: any}

is equivalent to

type State = { 
  idle: any;
  loading: any;
}

Therefor, the following code is invalid

const foo: State = {
  idle: true
}

because it's missing the prop loading.

A solution - make the props optional

type Status = 'idle' | 'loading' | 'error' | 'view'
type Actions = 'search' | 'search_success' | 'search_failure'

type States = { [key in Status]?: { [key in Actions]?: Status } };

const app: States = {
  idle: {
    search: 'loading'
  },
  loading: {
    search_success: 'view',
    search_failure: 'error',
  },
  error: {
    search: 'loading'
  },
  view: {
    search: 'loading'
  }
}

playground

The answer from @MoshFeu looks like it'll work fine, but as I've written this up, I'll post it to say that you can get more or less the same thing with a Partial:

type Status = 'idle' | 'loading' | 'error' | 'view'
type Actions = 'search' | 'search_success' | 'search_failure'

type States = { [key in Status]: Partial<{ [key in Actions]: Status }> };

const app: States = {
  idle: {
    search: 'loading'
  },
  loading: {
    search_success: 'view',
    search_failure: 'error',
  },
  error: {
    search: 'loading'
  },
  view: {
    search: 'loading'
  }
};
Related