简体   繁体   中英

React TypeScript: Argument is not assignable to parameter of type 'never'

My Router has a useReducer and the initialState has the error Argument of type 'StateInterface' is not assignable to parameter of type 'never'. this only started happening when I use action.value in the reducer in globalState.tsx

(Router.tsx)

    import React, { useReducer } from 'react';
    import { BrowserRouter, Route, Switch } from 'react-router-dom';
    import { Layout } from './components/layout';
    import { Dispatch, Global, InitialState, Reducer } from './globalState';
    import { Home } from './routes/home';
    import { NotFound } from './routes/notFound';

    const Router: React.FC = () => {
      const [global, dispatch] = useReducer(Reducer, InitialState); //<==== Here is the error
      return (
        <Dispatch.Provider value={{ dispatch }}>
          <Global.Provider value={{ global }}>
            <BrowserRouter>
              <Route
                render={({ location }) => (
                  <Layout location={location}>
                    <Switch location={location}>
                      <Route exact path='/' component={Home} />
                      <Route component={NotFound} />
                    </Switch>
                  </Layout>
                )}
              />
            </BrowserRouter>
          </Global.Provider>
        </Dispatch.Provider>
      );
    };

    export { Router };

then global state looks like

(globalState.tsx)

    import { createContext } from 'react';

    interface StateInterface {
      warningMessage: string;
      warningShow: boolean;
    }

    interface ActionInterface {
      type: string;
      value: string | boolean;
    }

    const Dispatch = createContext({
      dispatch: (action: ActionInterface) => {
        //
      },
    });

    const InitialState: StateInterface = {
      warningMessage: '',
      warningShow: false,
    };

    const Global = createContext({
      global: InitialState,
    });

    const WARNING_TOGGLE = 'WARNING_TOGGLE';
    const WARNING_MESSAGE = 'WARNING_MESSAGE';

    const Reducer = (state: StateInterface, action: ActionInterface) => { // <= if I change ActionInterface to any the probem goes away, but my linter then fires of with no any allowed
      switch (action.type) {
        case 'WARNING_TOGGLE':
          return {
            ...state,
            warningShow: action.value, // <== If I remove `action.value and set this to `true` the error goes away
          };
        case 'WARNING_MESSAGE':
          return {
            ...state,
            warningMessage: action.value, // <== If I remove `action.value and set this to `some string` the error goes away
          };
        default:
          return state;
      }
    };

    export { Reducer, InitialState, Dispatch, Global, WARNING_TOGGLE, WARNING_MESSAGE };

and Home

(home.tsx)

    import React, { useContext } from 'react';
    import { Dispatch, Global, WARNING_MESSAGE, WARNING_TOGGLE} from '../globalState';

    const Home: React.FC = () => {
      const { global } = useContext(Global);
      const { dispatch } = useContext(Dispatch);

      const openWarning = () => {
        dispatch({ type: WARNING_TOGGLE, value: true});
      };

      const closeWarning = () => {
        dispatch({ type: WARNING_TOGGLE, value: false});
      };

      const warningMessageVerify = () => {
        dispatch({ type: WARNING_MESSAGE, value: 'Please verify your email'});
      };

      const warningMessageOffine = () => {
        dispatch({ type: WARNING_MESSAGE, value: 'You are now offline'});
      };
      return (
      <section>
      {global.warningShow && <div>Warning Open</div>}
      {!global.warningShow && <div>Warning Closed</div>}
      {global.warningMessage}
      <br/>
        <button onClick={openWarning}>Open Warning</button>
        <button onClick={closeWarning}>Close Warning</button>
        <button onClick={warningMessageVerify}>Verify Email</button>
        <button onClick={warningMessageOffine}>Offline</button>
      </section>
      );
    };

    export { Home };

Typescript is not able to determine a safe typecast from IActionInterface value: string | boolean value: string | boolean to StateInterface warningShow: boolean , in this code - warningShow: action.value . It cannot safely presume that value will only contain a boolean.

There are several ways to suppress this, a simple one is -

warningShow: action.value as boolean

warningMessage: action.value as string

The as keyword will assert that the type of value is a boolean or string .

it is a best practice to define types for action creators separately.

Instead of

interface ActionInterface {
   type: string;
   value: string | boolean;
}

use two interface and define the type based on that

interface IActionInterface {
    type: string;
}

interface IMessageActionInterface extends IActionInterface { 
    value: string;
}

interface IShowActionInterface extends IActionInterface {
   value: boolean;
}

type ActionTypes = IMessageActionInterface | IShowActionInterface;

In Reducer

const Reducer = (state: StateInterface, action: ActionTypes) => { }

This seems odd in that if you do not explicitly return something from an arrow function. I believe that is construed as a "never". So, to me, this seems like the problem. If I am wrong, sorry about that.

const Dispatch = createContext({
  dispatch: (action: ActionInterface) => {
    //
  },
});

The issue is related to the way you constructed your ActionInterface type and used it in the reducer.

What you need is to have a way for TypeScript to differentiate the action structure when you pass it to the switch statement.

The best way to solve this is by simply give a hint to TypeScript about your actions and the union type you have.

Look at this example in TypeScript playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM