Skip to content

Frontend Actions

caasi Huang edited this page Dec 5, 2018 · 11 revisions

There are two kinds of Redux actions: plain actions and thunk actions.

Plain Action

Plain actions are objects with a type field and a payload field.

type PlainAction = {
  type: string,
  payload: any,
};

An ordinary plain action creator will be like:

const SET_BASE_LEVEL = 'SET_BASE_LEVEL';
const setBaseLevel =
  (baseLevel: ZoomLevel) => ({
    type: SET_BASE_LEVEL as typeof SET_BASE_LEVEL,
    payload: { baseLevel },
  });

The setBaseLevel function is a plain action creator (or a plain action data constructor if you treat an action a type) and it returns a plain action.

emptyAction

emptyAction is an action with a null type. It's the fallback action of many reducers, so those reducers won't have to check if the type field exists or not.

export const emptyAction = { type: null };

ActionUnion Type

The ActionUnion type is a type level function to extract return types of a map object full of action creators. So we can just create our action types and action creator functions instead of type the action first.

type FunctionType = (...args: any[]) => any;
type ActionCreatorsMap = { [actionCreator: string]: FunctionType };
export type ActionUnion<A extends ActionCreatorsMap> = typeof emptyAction | ReturnType<A[keyof A]>;

Thunk Action

Thunk actions are async functions with the ability to access current application states and dispatch other actions. A typical thunk action may look like this:

const panToObject =
  (oid: ObjectID) =>
  async (dispatch: Dispatch, getState: GetState) => {
    const { senseObject, viewport: v } = getState();
    const { x = 0, y = 0 } = CS.getObject(senseObject, oid);
    const pt = { x: v.width / 2 - x, y: v.height / 2 - y };
    dispatch(panViewport(pt));
  };

If you view thunk actions as a generic type:

type ThunkAction<T> = (dispatch: Dispatch, getState: GetState) => Promise<T>;

Then the type of thunk action creators is:

type ThunkActionCreator<T> = (...args: any[]) => ThunkAction<T>;

Error Handling

Thunk actions may handle errors if they have to use those errors to make decisions. You should handle other errors in your UI event handlers so you may not crash the whole application.

How to compose actions

A thunk action dispatch many thunk actions or plain actions. It makes the thunk action a effective action stream for Redux architecture. With the power of async/await, you can compose actions by hand or create some helper functions for them.

export const submitNewPassword =
  () =>
  async (dispatch: Dispatch, getState: GetState) => {
    const state = getState();
    const { session: { user }, settings } = state;
    const oldPassword = getOldPassword(settings);
    const newPassword = getNewPassword(settings);

    const r0 = await U.verifyPassword(user, oldPassword);
    if (!r0) {
      return dispatch(updatePasswordStatus(PasswordStatus.OLD_PASSWORD_WRONG));
    }
    const r1 = await U.updatePassword(user, newPassword);
    if (!r1) {
      return dispatch(updatePasswordStatus(PasswordStatus.NEW_PASSWORD_INVALID));
    }
    return dispatch(updatePasswordStatus(PasswordStatus.SUCCESS));
  };

mapDispatch

The mapDispatch function in src/types/map-dispatch.ts is a tool to apply the Redux dispatch function to many actions. It's not well typed and we need help to type it properly. We use it in almost any connected UI component.