Typescript: Create a Union from a Type

- 3 min read

How can you transform a Typescript Type into a Union Type? And most important, Why would you want to do that?

A few days ago, I encountered myself refactoring a code from using multiple useState that handled related states into a useReducer.

The issue came because the state was a vast set of properties, and I wanted to cut back the time of refactoring, so I want to reuse some of the previous code.

So, I decided that I needed to create the actions for this new reducer based on the names of the State, so basically created a union type based on the State type.

sponsor

El contenido de este sitio es y será siempre gratuito para todos. Ayudame a mantenerlo así convirtiendote en auspiciador.
Matias Hernández Logo

Tu producto o servicio podría estar aquí

So, the use case:

  • I had a type to represent a State
  • I want to create a list of actions based on that State

The goal was to do something like the following snippet.

typescript
					
						
type State = {
  searchKeyWords: string;
  isSlidingPanelOpen: boolean;
  selectedJobDocumentId: number;
}

// I want to create this

type Actions = {
  type: 'searchKeyWords',
  payload: string
} | {
  type: 'isSlidingPanelOpen',
  payload: boolean
} | {
  type: 'selectedJobDocumentId',
  payload: number
}

/// So it can be used like this
function reducer(state: State, action: Actions) {
  switch(action.type){
    case 'searchKeyWords':
      return {
        ...state,
        searchKeyWords: action.payload
      }
    case 'isSlidingPanelOpen':
      return {
          ...state,
          isSlidingPanelOpen: action.payload
        }
    case 'selectedJobDocumentId':
      return {
        ...state,
        selectedJobDocumentId: action.payload
      }
    default:
      return state
  }
}
					
				

The idea is to automatically take the State type to create the Actions type.

I needed a utility type that takes in an unknown type and spits out a Union with a specific shape to achieve this behavior.

So, a clear use case for Generics and Mapped Types.

🤔 Why? Because generics are the way to write reusable types.

More on that in this Twitter thread

Let’s create a new Unionize utility type that will take a generic named T that extends an object.

This type will iterate over the keys of T and map those to a new shape where each key of T will hold a new object shape.

typescript
					
						
/**
 * Create an Union type from an Object type
 * that will use the Object key as `type` entry and the
 * Object value as `payload`
 */
type Unionize<T extends object> = {
    [k in keyof T]: { type: k; payload: T[k] };
}[keyof T];
					
				

Let’s use it!!

typescript
					
						
type State = {
  searchKeyWords: string;
  isSlidingPanelOpen: boolean;
  selectedJobDocumentId: number;
}

/**
 * Create an Union type from an Object type
 * that will use the Object key as `type` entry and the
 * Object value as `payload`
 */
type Unionize<T extends object> = {
    [k in keyof T]: { type: k; payload: T[k] };
}[keyof T];

// I want to create this

type Actions = Unionize<State>

/// So it can be used like this
function reducer(state: State, action: Actions) {
  switch(action.type){
    case 'searchKeyWords':
      return {
        ...state,
        searchKeyWords: action.payload
      }
    case 'isSlidingPanelOpen':
      return {
          ...state,
          isSlidingPanelOpen: action.payload
        }
    case 'selectedJobDocumentId':
      return {
        ...state,
        selectedJobDocumentId: action.payload
      }
    default:
      return state
  }
}
					
				

Check it out in the typescript playground

😃 Thanks for reading!

Did you like the content? Found more content like this by joining to the Newsletter or following me on Twitter

⚙️ Edit this on github

📖 Keep reading