Avoiding type assertion when using useSelector with Redux, Immutable.js, and TypeScript

138 views Asked by At

I'm currently working with a combination of Redux, Immutable.js, and TypeScript. I'm having trouble getting proper types from the useSelector hook, and I've resorted to using type assertions. I understand that this is not ideal, and I'm looking for a better way to handle this situation.

Here's an example of my code:

  const selectedConInfo = useSelector(
    (state: RootState) => state.conversations.toJS().selectedConInfo,
  ) as Conversation;
  const emailsState = useSelector((state: RootState) =>
    state.emails.get('emails').toJS(),
  );
  const currentEmails = emailsState[selectedConInfo.id] as Email[];

I would like to avoid using type assertions (as Conversation and as Email[]) in this code. However, I'm not getting the proper types without them.

Here's the relevant code:

export const rootReducer = combineReducers({
  emails: emailsReducer,
});

export type RootState = ReturnType<typeof rootReducer>;

---
const initialState = {
  loading: false,
  emails: Map<string, List<Email>>({}),
  error: null,
};

const emailsReducer = (state = Map(initialState), action: EmailAction) => {
  switch (action.type) {
    case EMAILS_FETCH_REQUEST:
      return state.set('loading', true).set('error', null);
    case EMAILS_FETCH_SUCCESS:
      return state
        .set('loading', false)
        .setIn(['emails', action.meta], List(action.payload.data));

...
---


function EmailArea() {
  const dispatch = useDispatch();
  const selectedConInfo = useSelector(
    (state: RootState) => state.conversations.toJS().selectedConInfo,
  ) as Conversation;
  const emailsState = useSelector((state: RootState) =>
    state.emails.get('emails').toJS(),
  );
  const currentEmails = emailsState[selectedConInfo.id] as Email[];

I would greatly appreciate any suggestions on how to improve the type inference in this situation. Thank you!

Happy coding!

1

There are 1 answers

0
Dino.M On

I had the same struggle. I recommend using Record instead of Map if the type is set. Additionally, you can utilize Immutable.js's List and Map similar to JavaScript's Array and Map. So, by changing Map to Record and removing toJS, you should obtain the expected type and be able to use it with TypeScript. The following code snippet demonstrates how I would implement these changes:

const initialState: EmailState = {
  loading: false,
  emails: Map<string, Record<EmailInfo>>(),
  error: null,
};

...

function EmailArea() {
  const dispatch = useDispatch();
  const selectedConInfo = useSelector(
    (state: RootState) => state.conversations.selectedConInfo,
  );
  const emailsState = useSelector((state: RootState) =>
    state.emails.get('emails'),
  );

I hope this revised explanation is smoother and clearer.