Search code examples
javascriptreactjstypescriptautocompletereact-context

typescript typing isn't working as intended, how can I solve this?


I'm a noob typescript developer, I'm working on a projct where I will have a context API that will manage my state.

I've tried many scenarios to resolve the reducer problem but I couldn't, any help is appreciated.

import React, { useReducer } from "react";

type ActionType = { type: string; payload: any };
type StateType = { [key: string]: any };
type Dispatch = (action: ActionType) => void;
type ActionFunc = (dispatch: Dispatch) => Promise<void>;

type Actions<A extends { [key: string]: ActionFunc }> = {
  [key in keyof A]: ReturnType<A[keyof A]>;
};

type Reducer<S extends StateType, A extends ActionType> = (
  state: S,
  action: A
) => {
  [key in keyof S]: any;
};

type InitializeContext<State, Action extends ActionType> = (
  reducer: Reducer<State, Action>,
  actions: { [key: string]: ActionFunc },
  initialState: { [key in keyof State]: keyof State[key] }
) => void;
//---
//Test
//---
type TestAction = {
  type: "@ADD_PRODUCT";
  payload: {
    id: number;
    name: string;
    isCat: boolean;
  };
};

type TestState = { products: Array<TestAction["payload"]> };

const initialState: TestState = {
  products: []
};

const reducer: Reducer<TestState, TestAction> = (state, action) => {
  switch (action.type) {
    case "@ADD_PRODUCT":
      return {
        products: [...state.products, action.payload]
      };
    default:
      return state;
  }
};

const addProduct = (dispatch: Dispatch) => async () => {};

//------
//EndTest
//------
const initializeContext: InitializeContext<StateType, ActionType> = (
  reducer,
  actions,
  initialState
) => {
  const ContextApi = React.createContext({});

  const Provider: React.FC = (props) => {
    const [state, dispatch] = useReducer(reducer, initialState);

    const boundActions: Actions<typeof actions> = {};

    for (const key in actions) {
      if (Object.prototype.isPrototypeOf.call(actions, key)) {
        boundActions[key] = actions[key](dispatch);
      }
    }

    return (
      <ContextApi.Provider value={{ state, ...boundActions }}>
        {props.children}
      </ContextApi.Provider>
    );
  };
  // return { ContextApi, Provider }
};

initializeContext(reducer, { addProduct }, initialState); // why Type 'StateType' is not assignable to type 'TestState'

Codebox: https://codesandbox.io/s/typescript-playground-export-forked-u8ehp?file=/index.tsx:1922-1981

Playground


Solution

  • On L21, you set the type of reducer to Reducer<State, Action>. The actual reducer you assigned it is of a different type. While it might seem helpful to try to narrow the specificity on what will be passed to the Reducer, in practice this would be unenforceable: Redux will run every reducer for every action.

    You can import { AnyAction } from 'redux'; and use this for the action type, and let the value of state be set via inference.