Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 703
joshuaaron Avatar asked Mar 10 '26 09:03

joshuaaron


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

like image 96
Mosh Feu Avatar answered Mar 12 '26 23:03

Mosh Feu


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'
  }
};
like image 25
OliverRadini Avatar answered Mar 12 '26 22:03

OliverRadini