Reputation: 1343
I'm following along the Redux Writing Test guide to test my Redux/TypeScript application. I ran into TypeError: store.dispatch is not a function
error.
I followed this suggestion of exporting and consuming an already configured store object but I still receive the error. What am I missing?
(my attempt)
export default function setupStore(preloadedState?: PreloadedState<RootState>) {
return configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
preloadedState,
});
};
Here are the files with the related code.
test-utils.tsx
import setupStore from '../store';
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
preloadedState?: PreloadedState<RootState>;
store?: AppStore;
}
export function renderWithProviders(
ui: React.ReactElement,
{
preloadedState = {},
store = setupStore(preloadedState),
...renderOptions
}: ExtendedRenderOptions = {},
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>;
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}
store.ts
const persistConfig = {
key: 'root',
version: 1,
storage,
};
const rootReducer = combineReducers({ user: userReducer, cart: cartReducer });
const persistedReducer = persistReducer(persistConfig, rootReducer);
export default function setupStore(preloadedState?: PreloadedState<RootState>) {
return configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
preloadedState,
});
};
export let persistor = persistStore(setupStore as any);
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore['dispatch'];
Navbar.tsx
const Navbar: React.FC = () => {
const cartQuantity = useAppSelector((state: RootState) => state.cart.cartQuantity);
const { currentUser } = useAppSelector((state: RootState) => state.user);
return (
<Language>EN</Language>
);
};
Navbar.test.tsx
it('renders same span text in Navbar component', async () => {
renderWithProviders(<Navbar />);
const spanElement = screen.getByText(/EN/i);
expect(spanElement).toBeInTheDocument();
});
Upvotes: 1
Views: 782
Reputation: 202608
I think you've basically about the same issue as the post you linked to with the setupStore
function and needing to call it to get an actual store
object.
AppDispatch
needs to be the typeof store.dispatch
. Try the following where AppDispatch
is "picked" from the AppStore
type.
store.ts
export default function setupStore(preloadedState?: PreloadedState<RootState>) {
return configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
preloadedState,
});
};
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = Pick<AppStore, "dispatch">;
index.tsx - Import setupStore
and persistStore
here and instantiate the store
persistor
objects.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import persistStore from "redux-persist/es/persistStore";
import setupStore from "./store";
const store = setupStore();
const persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById("root")
);
Upvotes: 2