8

I have a type for my state, as well as an object of message handlers (thunks), which is explicitly typed as Record<string, (val: any) => AppThunk>

I get incoming messages that are objects, and their keys could match up with keys of the message handler object, in which case I want to call the message handler, OR their keys could match up with keys of the DataState, in which case I want to simply send them through to updateState.

The code below works fine.

type DataState = {
  startRequestCounter: -1,
  startResponseCounter: -1,
  stopRequestCounter: -1,
  stopResponseCounter: -1,
  robotMode: RobotMode.Idle as RobotMode,
}

const messageHandlers: Record<string, (val: any) => AppThunk> = {  
  startRequestCounter: () => async (dispatch, getState) => // do stuff,  
  startResponseCounter: (counter: number) => dispatch  => // do stuff,
  stopResponseCounter: (counter: number) => dispatch  => // do stuff
}

export const handleMessage = (message: MessagePayload): AppThunk => (dispatch, getState) => {
  Object.entries(message).forEach(([snakeKey, value]) => {
    const key = camelCase(snakeKey)
    
    // call the message handler for this message, if there is one
    if (messageHandlers[key]) {
      dispatch(messageHandlers[key](value))
    }
    // if not, update the state if we have a state for this message
    else if (Object.keys(getState().data).includes(key)) {  // <~~~~ HERE!!!!
      dispatch(updateState({ [key]: value }))
    }
  })
}

However, I feel like the else if at the end could be written more concisely.

else if (getState().data[key]) works (if I ts-ignore it), but TypeScript complains:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ startRequestCounter: number; startResponseCounter: number; stopRequestCounter: number; stopResponseCounter: number; cuvettesCount: number; pipettesCount: number; robotMode: RobotMode; }'.

I've done a little searching, and it looks like keyof might be what I'm looking for, but keyof can't even be found when I type it.

2 Answers 2

4

Todd's answer is good, but I figured one out that is basically as concise as I was looking for:

else if (key in getState().data)
Sign up to request clarification or add additional context in comments.

1 Comment

I was trying to do a check like this: if (obj[key]) before using obj[key] and your solution works in that situation.
3

It's because key is not guaranteed to be one of the keys listed in your DataState.

You can tell TypeScript that it will be using keyof.

if (getState().data[key]) { 
    // Error Here
}
if (getState().data[key as keyof DataState]) {  
    // Not Here
}

4 Comments

Does that mean I could say if (key is keyof DataState)?
No, you can't do that. as is just a way to tell TypeScript that you want to assume the variable is of this type even though you can't prove it. When that gets compiled back to JavaScript, it gets removed, because it doesn't really do anything.
You can create a TypeGuard that would kind of do that, but you're still writing the same logic out in that method. typescriptlang.org/docs/handbook/…
You have to realize that Typescript types don't actually exist. They just help you code. Therefore, you can't do anything based on if something is a "type".

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.