Skip to content

Instantly share code, notes, and snippets.

@steveadams
Created June 25, 2020 15:10
Show Gist options
  • Save steveadams/d716bfd2f966a194a1270f8aa93ee9d2 to your computer and use it in GitHub Desktop.
Save steveadams/d716bfd2f966a194a1270f8aa93ee9d2 to your computer and use it in GitHub Desktop.
How to type disaptch maps in TypeScript?
// This is an attempt to understand how to solve this problem better.
// I have objects which I use as dispatch maps, or dispatch tables, however you like to call them.
// I want to type them so I'm certain I'm always using the right identifiers. Excuse the code, it's
// arbitrary but roughly describes how I'm using it.
const stringKeyedIcons: Record<string, Icon> = {
Facebook: () => { /* facebook icon */ },
Google: () => { /* google icon */ },
Twitter: () => { /* twitter icon */ },
}
// Anything can go here, but I'll just use a void function
type Icon = () => void;
type IconName = keyof typeof stringKeyedIcons;
// Now when I want to reference icons, I can safely by using IconName:
const IconWrapper = (name: IconName) => {
// I know I can reference icons[name]!
}
// However, when I use IconWrapper I need to reference IconName using strings.
// My IDE offers suggestions and autocomplete, but this feels flimsy.
// For example, what if I rename a key of my object?
// const stringKeyedIcons: Record<string, Icon> = {
// Macebook: () => { /* renamed because facebook became medievil war reenactment social platform */ },
// Noogle: () => { /* google merged with Nokia, took their N */ },
// Zwitter: () => { /* Trying to stay hip I guess */ },
// }
// My IDE has no idea, and I need to manually update all usages of IconWrapper:
// IconWrapper('Facebook') <-- This would be an error, and it would be scattered around my application
// Well, here's an alternative. I can use an enum which reflects my object's structure:
enum IconName {
Facebook,
Google,
Twitter
}
const enumKeyedIcons: Record<IconName, Icon> = {
[IconName.Facebook]: () => { /* facebook icon */ },
[IconName.Google]: () => { /* google icon */ },
[IconName.Twitter]: () => { /* twitter icon */ },
}
// This looks a little better in some ways, although it's fairly verbose. But I'm okay with that
// because I can rename my icons easily since I'll be using references to the enum.
//
// However, in cases where I need the enum value to match external sources, I need to use a
// non-numeric enum. For example, I've named notices that can be showed to a user, but I'm keeping
// track of those in localStorage so I don't show them more than once. I need detailed names for the
// notices so I don't get naming collisions, and it's clear how it's being used in the code:
enum NoticeID {
'NoticeTopic_2020_03_22' = 'NoticeTopic_2020_03_22',
'NoticeTopic_2020_04_13' = 'NoticeTopic_2020_04_13',
'NoticeTopic_2020_06_18' = 'NoticeTopic_2020_06_18'
}
const notices: Record<NoticeID, string> = {
[NoticeID.NoticeTopic_2020_03_22]: () => 'NoticeValue',
[NoticeID.NoticeTopic_2020_04_13]: () => 'NoticeValue',
[NoticeID.NoticeTopic_2020_06_18]: () => 'NoticeValue'
}
// Now when I trigger a notice in the application:
// actions.triggerNotice(Notices.NoticeTopic_2020_06_18);
// I can carry that NoticeID all the way through the dispatch and, once the notice is dismissed,
// mark that NoticeID as read in localStorage.
//
// The trouble is, this seems _really_ verbose and clumsy
//
// What I wonder is: What's a better way to do this? What am I missing in TypeScript that solves this better?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment