redux-toolkit: Uncaught TypeError: Object(...) is not a function with createStore
I’m on a Server-Side rendering project featuring Express, React, Redux, Apollo and I faced this annoying problem that has bugged me for so long.
// shared/redux/store.ts
import { createStore, applyMiddleware, Store } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers";
type StoreProps = {
initialState?: { [k: string]: any };
middleware?: any[];
};
const composeEnhancers = composeWithDevTools({
actionsBlacklist: [],
});
const configureStore = ({ initialState }: StoreProps): Store => {
const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...[thunk]))
);
return store;
};
export default configureStore;
I followed the official Redux SSR and Static Typing posts.
I also make a twist by adding the store like Express’s middleware for further usage.
// server/middlewares/globalStore.ts
import { Response, Request, NextFunction } from "express";
import configureStore from "@common/redux/store";
const globalStore = (
_req: Request,
res: Response,
next: NextFunction
): void => {
res.locals.store = configureStore({});
next();
};
export default globalStore;
// server/index.ts
import {SSR, globalStore} from "@server/middlewares";
const app = express();
app.use(globalStore);
app.get("*", SSR());
app.listen(3000, () => {
console.log("`✅✅✅ Server is running at port 3000 ✅✅✅");
});
Here are my sever side and client side
// server/middlewares/SSR
import React from "react";
import { RequestHandler, Request, Response } from "express";
import { Provider } from "react-redux";
import { StaticRouterContext } from "react-router";
import { StaticRouter } from "react-router-dom";
import { renderToString } from "react-dom/server";
import { Helmet, HelmetData } from "react-helmet";
import { ApolloProvider } from "@apollo/react-hooks";
import SchemaLink from "apollo-link-schema";
import App from "@common/App";
import { createApolloClient } from "@common/utils/apolloLinks";
import schema from "@server/utils/gqlCombine";
interface HTMLProps {
content: string;
scripts: Array<string>;
css: Array<string>;
helmetContext: HelmetData;
apolloStates: Record<string, unknown>;
reduxStates: Record<string, unknown>;
}
const HTML: React.FC<HTMLProps> = ({
scripts,
css,
content,
helmetContext,
apolloStates,
reduxStates,
}) => {
const htmlAttrs = helmetContext.htmlAttributes.toComponent();
const bodyAttrs = helmetContext.bodyAttributes.toComponent();
return (
<html {...htmlAttrs}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{helmetContext.title.toComponent()}
{helmetContext.meta.toComponent()}
{helmetContext.link.toComponent()}
{css.filter(Boolean).map((href) => (
<link key={href} rel="stylesheet" href={href} />
))}
<script
dangerouslySetInnerHTML={{
__html: `
window.__APOLLO_STATE__=${JSON.stringify(apolloStates).replace(
/>/g,
"\\u003c"
)};
window.__PRELOADED_STATE__=${JSON.stringify(reduxStates).replace(
/>/g,
"\u003c"
)};
`,
}}
/>
</head>
<body {...bodyAttrs}>
<div id="app" dangerouslySetInnerHTML={{ __html: content }} />
{scripts.filter(Boolean).map((src) => (
<script key={src} src={src} />
))}
</body>
</html>
);
};
const SSR = (): RequestHandler => (req: Request, res: Response): any => {
// const baseUrl = `${req.protocol}://${req.get("Host")}`;
const client = createApolloClient({ link: new SchemaLink({ schema }) });
const context: StaticRouterContext = {};
const jsx = (
<ApolloProvider client={client}>
<Provider store={res.locals.store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
</ApolloProvider>
);
const content = renderToString(jsx);
const helmet = Helmet.renderStatic();
return res.send(
"<!doctype html>" +
renderToString(
<HTML
css={[res.locals.assetPath("bundle.css")]}
helmetContext={helmet}
scripts={[
res.locals.assetPath("bundle.js"),
res.locals.assetPath("vendor.js"),
]}
reduxStates={res.locals.store.getState()}
apolloStates={client.extract()}
content={content}
></HTML>
)
);
};
export default SSR;
// client/index.tsx
import React from "react";
import { Provider } from "react-redux";
import { hydrate } from "react-dom";
import { Router } from "react-router-dom";
import { ApolloProvider } from "@apollo/react-hooks";
import { HttpLink, ApolloLink, Observable } from "apollo-boost";
import { TokenRefreshLink } from "apollo-link-token-refresh";
import { onError } from "apollo-link-error";
import jwt from "jsonwebtoken";
import App from "@common/App";
import createHistory from "@common/utils/store";
import { createApolloClient } from "@common/utils/apolloLinks";
import { getAccessToken, setAccessToken } from "@common/utils/tokenStore";
import configureStore from "@common/redux/store";
const preloadedState = window.__PRELOADED_STATE__;
delete window.__PRELOADED_STATE__;
const history = createHistory();
const store = configureStore({
initialState: preloadedState,
});
const httpLink = () => new HttpLink({ uri: "/api/graphql" });
const errorLink = () =>
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ extensions }) => {
console.log(extensions);
});
}
if (networkError) {
console.log(networkError);
}
});
const authLink = () =>
new ApolloLink(
(operation, forward) =>
new Observable((observer) => {
let handle: any;
Promise.resolve(operation)
.then((operation) => {
const accessToken = getAccessToken();
if (accessToken) {
operation.setContext({
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
}
})
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const tokenLink = () =>
new TokenRefreshLink({
accessTokenField: "accessToken",
isTokenValidOrUndefined: () => {
const token = getAccessToken();
if (!token) {
return true;
}
try {
//@ts-ignore
const { exp } = jwt.decode(token);
if (Date.now() >= exp * 1000) {
return false;
} else {
return true;
}
} catch {
return false;
}
},
fetchAccessToken: () => {
const url =
typeof window !== "undefined"
? `${window.location.origin}/api/refresh_token`
: "/api/refresh_token";
return fetch(url, {
method: "POST",
credentials: "include",
});
},
handleFetch: (accessToken) => {
setAccessToken(accessToken);
},
handleError: () => {
console.warn("invalid refesh_token");
},
}) as any;
const client = createApolloClient({
link: ApolloLink.from([tokenLink(), errorLink(), authLink(), httpLink()]),
});
hydrate(
<ApolloProvider client={client}>
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>
</ApolloProvider>,
document.getElementById("app")
);
if (process.env.NODE_ENV === "development") {
if (module.hot) {
module.hot.accept();
}
if (!window.store) {
window.store = store;
}
}
I use Webpack to run the project. I set it up one by one (Express + Webpack + Typescript -> React -> Apollo -> Redux). Everything had been doing well before I added Redux so I don’t think it’s a problem of other libraries. The error I got is from the client side, not from the server side. Another problem I has encountered is that Redux apparently can’t be loaded in the project since the extension doesn’t light up. I don’t know what produces the bug so I’m unable to share more of my codebase. If you need more, I’m willing to share. Thanks for reading.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 15 (3 by maintainers)
For folks who run into this issue in the future, I was using a standard webpack production mode configuration with
optimization.splitChunks
chunking mynode_modules
withchunks: all
. I noticed redux was entirely missing from the chunk. It seems webpack was overeager to tree-shake out all references to the core redux this tool depends on which lead to lots ofis not a Function()
errors and the sorts.The solution above solved this issue. Thanks!
Ok. I checked my Webpack configuration and it seems like I need to alias every Redux imports to one package since there’re differences in Redux version among my libraries and frameworks. Just set in webpack configuration and it’s good to go. Still don’t know why ¯_(ツ)_/¯