Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve "Cannot invoke an expression whose type lacks a call signature" typescript error - Visual Studio C# React / Redux template

When I create a new project using the C# React/Redux Web Application template in Visual Studio, there is an error reported in the "\ClientApp\configureStore.ts" file.

"createStoreWithMiddleware(allReducers, initialState)" is underlined red and the error states:

"TS2349 (TS) Cannot invoke an expression whose type lacks a call signature. Type '{}' has no compatible call signatures."

REPRO STEPS:

  • Open VS 2017 Community Edition version 15.3.4
  • Create a new C# web project using .NET framework 4.7
  • Select the React/Redux Web Application template and .NET Core version 2.0
  • When the project loads, npm dependencies are missing, so I resolve dependencies by opening the project folder in the Package Manager console, and typing "npm install npm@latest -g"
  • After this the site will now load and the site appears to function fine. However, "createStoreWithMiddleware(allReducers, initialState)" shows the error as described above.

I'm reluctant to simply ignore the error or suppress it, as I suspect this might be a simple case of either defining a call signature (or casting?). My Typescript knowledge is near non-existent, so apologies if this is an extremely basic question.

[UPDATE] - It turns out that if I remove the following line of code, the error is no longer displayed. Naturally this means the devTools extension won't work though, so I need to work out what this line's doing in order to get it working without throwing an error.

devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next

The code for the "configureStore.ts" file is below - thanks in advance for any help!

import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store, StoreEnhancerStoreCreator, ReducersMapObject } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as StoreModule from './store';
import { ApplicationState, reducers } from './store';
import { History } from 'history';

export default function configureStore(history: History, initialState?: ApplicationState) {
    // Build middleware. These are functions that can process the actions before they reach the store.
    const windowIfDefined = typeof window === 'undefined' ? null : window as any;
    // If devTools is installed, connect to it
    const devToolsExtension = windowIfDefined && windowIfDefined.devToolsExtension as () => GenericStoreEnhancer;
    const createStoreWithMiddleware = compose(
        applyMiddleware(thunk, routerMiddleware(history)),
        devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
    )(createStore);

    // Combine all reducers and instantiate the app-wide store instance
    const allReducers = buildRootReducer(reducers);
    const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;

    // Enable Webpack hot module replacement for reducers
    if (module.hot) {
        module.hot.accept('./store', () => {
            const nextRootReducer = require<typeof StoreModule>('./store');
            store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
        });
    }

    return store;
}

function buildRootReducer(allReducers: ReducersMapObject) {
    return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
}
like image 505
Tom Ell Avatar asked Sep 17 '17 16:09

Tom Ell


2 Answers

Unfortunately, I have no idea where the SPA project templates are developed, so I cannot check whether this is a known problem and whether a fix for this is already in development.

However, you can easily fix this issue for your local project by yourself. The problem here is that the compose function does not return a properly typed callable, so calling createStoreWithMiddleware is not allowed. You can easily fix this by overwriting the type information there; there are multiple ways to do this. One would be changing the line where the error appears to the following:

const store = (<any>createStoreWithMiddleware)(allReducers, initialState) as Store<ApplicationState>;

This casts createStoreWithMiddleware into the any type which basically tells TypeScript that it knows nothing about it and just accepts anything. Since the result of the function call is explicitly typed using the as Store<ApplicationState> anyway, this will not cause any problems later on either.

like image 27
poke Avatar answered Sep 28 '22 07:09

poke


This is because the non-generic compose() function returns a function with no defined parameters. The best fix for this error is to explicitly use the generic compose<T>() function that returns the base type of StoreEnhancerStoreCreator<any>. That type does explicitly take the two parameters you're trying to send, and calling createStoreWithMiddleware will now have full intellisense:

const createStoreWithMiddleware = compose<StoreEnhancerStoreCreator<any>>(
    applyMiddleware(thunk, routerMiddleware(history)),
    devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
)(createStore);

// unchanged:
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;

Try to avoid casting something as <any> (similar to C#'s object type) unless that is absolutely necessary. It removes the type checking of TypeScript, which is a core reason of using TypeScript. So if a function can only ever return Cat, Dog or Human, define it as Mammal. If you later add Snakes, you can define it as Animal.

like image 50
arserbin3 Avatar answered Sep 28 '22 07:09

arserbin3