Originally written at https://matiashernandez.dev
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.
My rule of thumb is: If you have 3 related
useState, you should move that to be auseReducer
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.
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.
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
Mapped Types:
The main idea of this is to take the properties > of a type
Tto create a new type by using those > properties in another way
Let's create a new Unionize utility type that will take a generic named T that extends an object.
The
extendskeyword here acts as a way to restrict the type ofTto be like anobject
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.
/** * 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!!
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
Follow me on Twitter ❤️ Support my content