States

Prepare Initial States + State Type

export const initialState = {
  attributeA: {
    propertyA1: ``,
  },
  attributeB: {
    propertyB1: 0,
  },
  num: 0,
};

export type StateType = {
  attributeA: {
    propertyA1: string;
  };
  attributeB: {
    propertyB1: number;
  };
  num: number;
};

Action

Prepare actions

type ActionMap<M extends { [index: string]: any }> = {
  [Key in keyof M]: M[Key] extends undefined
    ? {
        type: Key;
      }
    : {
        type: Key;
        payload: M[Key];
      };
};

// eslint-disable-next-line no-shadow
export enum ActionType {
  setAttrA = `SET_ATTR_A`,
  setAttrB = `SET_ATTR_B`,
  add = `ADD`,
  reduce = `REDUCE`,
}

/**
 * @name ActionPayload
 * @description Type def for action payloads,
 */
type ActionPayload = {
  [ActionType.setAttrA]: {
    propertyA1: string;
  };
  [ActionType.setAttrB]: {
    propertyB1: number;
  };
  /* Not payloads required for below 2 actions */
  [ActionType.add]: undefined;
  [ActionType.reduce]: undefined;
};

/**
 * @name ActionPayloadDataType
 * @description The type def of the Action Payload Data
 */
export type ActionPayloadDataType = ActionMap<ActionPayload>[keyof ActionMap<ActionPayload>];

Reducer

Prepare the reducer details, NO EDIT STATE DIRECTLY

export const reducers = (
  state: StateType,
  actionData: ActionPayloadDataType,
): StateType => {
  switch (actionData.type) {
    case ActionType.setAttrA:
      return {
        ...state,
        attributeA: {
          ...state.attributeA,
          ...actionData.payload,
        },
      };
    case ActionType.setAttrB:
      return {
        ...state,
        attributeB: {
          ...state.attributeB,
          ...actionData.payload,
        },
      };
    case ActionType.add:
      return {
        ...state,
        num: state.num + 1,
      };
    case ActionType.reduce:
      return {
        ...state,
        num: state.num - 1,
      };
    default:
      return state;
  }
};

Create Provider

Create a HOC for state provider

export const AppContext = createContext<{
  state: StateType;
  dispatch: React.Dispatch<ActionPayloadDataType>;
}>({
  state: initialState,
  dispatch: () => null,
});

/**
 * @name StateProvider
 * @description A HOC to wrap root components for global state manage
 */
export const StateProvider: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(reducers, initialState);

  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  );
};

Wrap your root Component


const App: React.FC = () => (
  <StateProvider>
    <RootComponent/>
  </StateProvider>
);

Access Context in any of child components

Now you can use it

const Camera: React.FC = () => {
  const { state, dispatch } = useContext(AppContext);

  return (
    <div>
      {state.num}
      <button onClick={dispatch({
        type: ActionType.add
      })}/>
      <button onClick={dispatch({
        type: ActionType.setAttrB,
        payload: {
          propertyB1: 'dispatch this action to set property B1'
        },
      })}/>
    </div>
  );
};

export default Camera;