0

I have some question about the type-save implementation of Redux in a Typescript React App.

I have done Setup and type-checking on Reducer and also using useTypedSelector from react-redux. Here, only one thing that i have inside the case Statements of Reducer a loose type-checking but that's not so problematically.

My Setup of the Redux Store:

// --------------------------------------- REDUX STORE ---------------------------------------
interface ITimePreset {
  session: number;
  break: number;
}

const UPDATE_TIMEPRESET = 'UPDATE_TIMEPRESET';
const INCREMENT_TIMEPRESET = 'INCREMENT_TIMEPRESET';
const DECREMENT_TIMEPRESET = 'DECREMENT_TIMEPRESET';
const BREAK = 'break';
const SESSION = 'session';

type Item = typeof BREAK | typeof SESSION;

interface IUpdateTimepresetAction {
  type: typeof UPDATE_TIMEPRESET;
  payload: {
    value: number;
    item: Item;
  };
}

interface IIncrementTimepresetAction {
  type: typeof INCREMENT_TIMEPRESET;
  payload: Item;
}

interface IDecrementTimepresetAction {
  type: typeof DECREMENT_TIMEPRESET;
  payload: Item;
}

type TimePresetActionTypes = IUpdateTimepresetAction | IIncrementTimepresetAction | IDecrementTimepresetAction;

const initialState: ITimePreset = {
  session: 25,
  break: 5,
};

const timePresetReducer = (state: ITimePreset = initialState, action: TimePresetActionTypes): ITimePreset => {
  switch (action.type) {
    case UPDATE_TIMEPRESET:
      return {
        ...state,
        [action.payload.item]: action.payload.value,
      };
    case INCREMENT_TIMEPRESET:
      return {
        ...state,
        [action.payload]: state[action.payload] + 1,
      };
    case DECREMENT_TIMEPRESET:
      return {
        ...state,
        [action.payload]: state[action.payload] - 1,
      };
    default:
      return state;
  }
};

const rootReducer = combineReducers({
  timePreset: timePresetReducer,
});

type RootState = ReturnType<typeof rootReducer>;

const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

const store = createStore(rootReducer);

Usage of the useTypedSelector with nice type-checking is pretty fine:

const timePreset: ITimePreset = useTypedSelector(state => state.timePreset);

But for useDispatch i doesn't found a Solution for type-checking:

const dispatch = useDispatch();

// This is the correct one:
dispatch({ type: DECREMENT_TIMEPRESET, payload: element });

// Wrong one with no error
dispatch({ type: 'Whaaat', payload: 9 });

Is there a way for a type-checking on useDispatch Parameters? Did you have other suggestions for Implementation of Redux Store (Some best Practices)-> This is my very first Example.

1 Answer 1

2

Few things which should help you here.

  1. One global ActionType type for all action types in the system.
type ActionType = 'UPDATE_TIMEPRESET' | 'INCREMENT_TIMEPRESET' | 'DECREMENT_TIMEPRESET';

Of course specific type can be important from modules, you don't need to have them in one place but for example

import SomethingActionType from 'someModule';
import Something2ActionType from 'someModule2';
type ActionType = SomethingActionType | Something2ActionType;

  1. Define Action type

We need to have one general definition of how our Action looks like. Example type:

type Action<P> = {
  type: ActionType,
  payload: P
}

Our Action is type constructor, we can now define specific types like Action<string> or Action<UserRequest>

  1. Define ActionCreator type

Next step is to define ActionCreator type. ActionCreator is just simple function which return a valid Action.

type ActionCreator<P> = (p: P) => Action<P>; 

  1. Join these concepts to create some action creators

const incrementTimePreset: ActionCreator<Item> = payload => ({
   type: 'INCREMENT_TIMEPRESET' // type safe as it goes from ActionType union
   payload
})

const decrementTimePreset: ActionCreator<Item> = payload => ({
   type: 'DECREMENT_TIMEPRESET' // type safe as it goes from ActionType union
   payload
})

  1. Using with useDispatch

Now istead of manual using useDispatch with some object literals ad hok, we will use action creators.

dispatch(decrementTimePreset(item))

  1. Custom hook only for our actions, optionally as previous steps are good enough
const useSafeDispatch = () => {
  const dispatch = useDispatch();
  return <P>(action: Action<P>) => dispatch(action);
}

// using
const dispatch = useSafeDispatch();
dispatch(decrementTimePreset(item)); // only proper action structure is allowed
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot for this Step by Step Guide ... Already at Step 5 all works like I expected :-) ... But i doesn´t understand Step 6 i think there is something missing ... Maybe you can check and explain me how it works?
Removed one typo check again please
Found the Problem ... implementation of useSafeDispatch not working in .tsx file -> Compiler Error while the Generics are interpreted as JSX Tags ... Tooo long Day.....

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.