const rootReducer = combineReducers({
router: routerStateReducer,
todos,
})
const createStoreWithMiddleware = compose(
applyMiddleware(thunk),
reduxReactRouter({ routes, createHistory })
)(createStore)(reducer);
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState)
It is giving me createStoreWithMiddleware is not a function.. Why this ?
You're executing the result of compose (which returns a function). So instead of setting createStoreWithMiddleware to the returned function, you are setting it to the executed result with the variable reducer.
Not sure what the variable reducer in this context is since you have rootReducer defined above. Your code should probably read:
const createStoreWithMiddleware = compose(
applyMiddleware(thunk),
reduxReactRouter({ routes, createHistory })
)(createStore)
doing so will define createStoreWithMiddleware as an extended createStore function, which can then receive your rootReducer and initialState.
Related
I have a unique situation in which my <Provider> component (and entire Redux Store) is exported from a middleware application to multiple front end React apps. The middleware has its own set of reducers but the client apps can inject their own reducers into the store when they call the provider.
It is now being asked that I accept an initial state (preloadedState) object when the Provider is called so that the initial state of the app can be loaded with dynamic initial state. This object will be an arbitrary set of state data (with corresponding reducers) so I'll have the data structures correctly with the shape in the reducers, but I won't know what values they're sending.
Here's the basic ReduxStore set up, changed here for simplicity:
ReduxStore.ts
import { configureStore } from '#reduxjs/toolkit';
import { ExampleReducer } from './slices/ExampleSlice';
export const reducer = {
example: ExampleReducer
};
const ReduxStore = configureStore({
middleware: ...,
reducer,
});
export default ReduxStore;
CoreProvider.tsx
import React from 'react';
import { Provider } from 'react-redux';
import { combineReducers, ReducersMapObject, AnyAction } from '#reduxjs/toolkit';
import ReduxStore, { reducer } from './ReduxStore';
export type ConfigProps = {
newReducers?: ReducersMapObject<unknown, AnyAction>;
preloadedState: Object;
};
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
}) => {
const newReducer = combineReducers({ ...newReducers, ...reducer });
ReduxStore.replaceReducer(newReducer);
return <Provider store={ReduxStore}>{children}</Provider>;
};
export default CoreProvider;
index.jsx
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CoreProvider newReducers={{ arbitrary: arbitraryReducer }}>
<App />
</CoreProvider>
</React.StrictMode>,
);
Now, I'm aware of the reasons I should NOT do this, and I am aware that initial state comes from the reducers themselves, which is handled when the reducers are merged with the default reducers when the Provider is called.
I know that configureStore accepts a preloadedState option but where I'm stuck is how to dynamically pass a preloadedState coming in as a prop from the Provider component. I have tried wrapping the configureStore call inside a function that is called within the Provider component, which does indeed set the initial state, but my ReduxStore export within ReduxStore.ts is undefined due to the way the app loads and the configureStore is called.
Provider calling configureStore with preloadedState props:
import { initReduxStore, reducer } from './ReduxStore';
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
preloadedState
}) => {
const newReducer = combineReducers({ ...newReducers, ...reducer });
const ReduxStore = initReduxStore(preloadedState, newReducer);
return <Provider store={ReduxStore}>{children}</Provider>;
};
export default CoreProvider;
ReduxStore with exported configureStore function and undefined ReduxStore export:
export const reducer = {
example: ExampleReducer
};
let ReduxStore;
export const initReduxStore = (incomingState, incomingReducer) => {
if (ReduxStore) return ReduxStore;
const store = configureStore({
middleware: ...,
preloadedState: incomingState,
reducer: incomingReducer,
});
ReduxStore = store;
return ReduxStore;
};
export default ReduxStore; // This becomes undefined because the file loads before the Provider component calls the initReduxStore function
The ReduxStore export is undefined because the file loads before the Provider component calls the initReduxStore function and ReduxStore is undefined at the time it's exported.
Is there a known way that I have overlooked to easily set the preloaded state object when the provider is called? Should I restructure how the ReduxStore is created?
In short terms, how do I get the preloadedState prop from my Provider to my configureStore, while still exporting the ReduxStore to the rest of the app?
export const ReduxStore = configureStore({
preloadedState,
reducer,
});
// HOW DO I CONNECT THESE
const CoreProvider: React.FC<ConfigProps> = ({
children,
newReducers,
preloadedState
}) => {
return <Provider store={store}>{children}</Provider>;
};
export default CoreProvider;
Any thoughts are appreciated.
The short answer is, you can't. The preloadedState option can only be passed in when you call configureStore(). Once the store is created, the only way to update the store state is to dispatch an action.
On top of that, behavior such as replacing the reducer or dispatching actions would qualify as a side effect, and React components must not have side effects directly in the rendering logic.
The closest suggestion I have would be a useLayoutEffect hook in this component that watches for changes to the provided reducers or state, and does the store.replaceReducer() call.
Also, you could have a wrapping reducer that watches for some kind of a "merge in this additional state" action, and returns the updated state with the additional fields.
But overall, this is a very unusual use case, and not something Redux is really designed for.
I'm using a different setup for initializing the redux store (I don't want to use the configureStore method from redux-toolkit) for the purpose of injecting reducers on runtime.
import {
createStore,
combineReducers,
applyMiddleware,
createImmutableStateInvariantMiddleware,
} from "#reduxjs/toolkit";
import { composeWithDevTools } from "#redux-devtools/extension";
import { createCustomMiddleWare} from "./myCustomMiddleWare";
const staticReducers = {};
const middlewares = [
createImmutableStateInvariantMiddleware(),
createCustomMiddleWare(),
];
const createReducer = (asyncReducers = {}) =>
combineReducers({
...staticReducers,
...asyncReducers,
});
export const initializeStore = (initializeState = {}) => {
const store = createStore(
createReducer(),
initializeState,
composeWithDevTools(applyMiddleware(...middlewares))
);
store.asyncReducers = {};
store.injectReducer = (key, reducer) => {
store.asyncReducers[key] = reducer;
store.replaceReducer(createReducer(store.asyncReducers));
return store;
};
return store;
};
export default initializeStore;
I was wondering wether there is a way to add Thunk middleware from redux-toolkit (without installing the redux-thunk package seperately)
I tried using the getDefaultMiddleware() method from redux-toolkit which imports three default middlewares that ships with redux-toolkit including thunk, but since I'm not using it as paramether for the configureStore's middleware callback, I get this error that getDefaultMiddleware is deprecated.
I was able to import one of the default middlewares by using createImmutableStateInvariantMiddleware() function that I found in the official documentations but I could not find a way to import thunk or serlizied middlewares (two other default middlewares of redux-toolkit)
The answer would be to use configureStore and using the middleware callback option.
There is really no good reason that would be using createStore here, that could come down to
const store = configureStore({
reducer: createReducer(),
preloadedState: initializeState,
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(createCustomMiddleWare())
})
I'm working on a project with the React Redux Router, and am trying to change the page. When a component dispatches a "push" action, I see the "##router/LOCATION_CHANGE" come through, but nothing changes.
I do have the routerMiddleware applied as well,
import thunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory'
import rootReducer from '../reducers';
export const history = createHistory();
const middleware = routerMiddleware(history)
export function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk),
applyMiddleware(middleware)
);
}
any thoughts as to what I'm doing incorrectly?
applyMiddleware expects a list of middlewares, so you're using it wrong:
Please change it to:
export const history = createHistory();
const middleware = routerMiddleware(history)
export function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk,middleware)
);
}
This also explains why switching them worked (the store received another middleware).
The redux getState method is not available. Do I need any middleware for achieving the desired result. Following is the code
const configureStore = (initialState = {}) => {
return createStore(
rootReducer,
initialState,
applyMiddleware(...middleware),
);
};
export default configureStore;
util.js
import store from '../../store';
store.getState()
// _store2.default.getState is not a function
You are returning a function instead of your store. Seems like you have mixed up reducers with the store. What you want is this:
const store = createStore(
rootReducer,
initialState,
applyMiddleware(...middleware),
);
export default store;
Looks like you intended to, at some point, initialize the store.
I'm not sure how you want to layout your app, but
// index.js
import configureStore from 'store';
export const store = configureStore({ ... someInitialState });
then
// util.js
import store from 'index';
// use store
I don't think this is the best solution - instead of exporting an store initializer from store, just initialize the store and export the store.
I am trying to call the reducer from the component and want to render that in component , but when I am trying to store the reducer in the createStore() method of redux above error is coming. My Code is like this:-
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import { browserHistory } from 'react-router'
import makeRootReducer from './reducers'
import { updateLocation } from './location'
import allReducers from './reducers';
export default (initialState = {}) => {
// ======================================================
// Middleware Configuration
// ======================================================
const middleware = [thunk]
// ======================================================
// Store Enhancers
// ======================================================
const enhancers = []
let composeEnhancers = compose
if (__DEV__) {
const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
if (typeof composeWithDevToolsExtension === 'function') {
composeEnhancers = composeWithDevToolsExtension
}
}
// ======================================================
// Store Instantiation and HMR Setup
// ======================================================
const store = createStore(
allReducers,
makeRootReducer(),
initialState,
composeEnhancers(
applyMiddleware(...middleware),
...enhancers
)
)
I am getting error :Uncaught Error: Expected the enhancer to be a
function
You are passing in two reducers to the createStore function instead of one.
Since the third argument to createStore is always the enhancer function it thinks that the 'initiaeState' variable is an enhancer function since you are passing this in as the thid argument to createStore.
createStore expects to receive the following arguments:
reducer (Function): A reducing function that returns the next state
tree, given the current state tree and an action to handle.
[preloadedState] (any): The initial state. You may optionally
specify it to hydrate the state from the server in universal apps,
or to restore a previously serialized user session. If you produced
reducer with combineReducers, this must be a plain object with the
same shape as the keys passed to it. Otherwise, you are free to pass
anything that your reducer can understand.
[enhancer] (Function): The store enhancer. You may optionally
specify it to enhance the store with third-party capabilities such
as middleware, time travel, persistence, etc. The only store
enhancer that ships with Redux is applyMiddleware().
Remember the root reducer in your app should combine all your reducers into one single reducer.
From the Redux docs
First and foremost, it's important to understand that your entire
application really only has one single reducer function
I encountered a similar error TypeError: enhancer(...) is not a function because I was passing a function instead of a plain object as the initial state.
Ie my error was due to missing brackets on my initial state generator function:
const store = createStore(
reducer,
testState, // <<< missing brackets!! doh
applyMiddleware(sagaMiddleware)
)
When it should have been:
const store = createStore(
reducer,
testState(),
applyMiddleware(sagaMiddleware)
)
In my case it was a wrong import.
❌ Wrong import:
import { applyMiddleware, createStore } from "redux";
import rootReducer from "../reducers/index";
import createSagaMiddleware from "redux-saga";
import rootSaga from "redux/sagas";
import { compose } from "#material-ui/system"; // <= ERROR: wrong compose
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
{},
composeEnhancers(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
export default store;
✅ Correct import:
import { applyMiddleware, compose, createStore } from "redux"; // <= Correct!
import createSagaMiddleware from "redux-saga";
import rootSaga from "redux/sagas";
import rootReducer from "../reducers/index";
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
{},
composeEnhancers(applyMiddleware(sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
export default store;