<Provider> does not support changing `store` on the fly.
remojansen opened this issue · comments
I'm trying to use redux with react-redux react-router-redux and redux-immutable and I get an error when the initial @@router/LOCATION_CHANGE
action is triggered:
action @ 14:19:07.625 @@router/LOCATION_CHANGE
%c prev state color: #9E9E9E; font-weight: bold Map { "repos": Map { "loading": false, "reposCount": 0 }, "users": Map { "loading": false, "usersCount": 0 }, "router": Map { "locationBeforeTransitions": null } }
%c action color: #03A9F4; font-weight: bold { type: '@@router/LOCATION_CHANGE',
payload:
{ pathname: 'blank',
search: '',
hash: '',
state: null,
action: 'POP',
key: '5b05pd',
query: {},
'$searchBase': { search: '', searchBase: '' } } }
%c next state color: #4CAF50; font-weight: bold Map { "repos": Map { "loading": false, "reposCount": 0 }, "users": Map { "loading": false, "usersCount": 0 }, "router": Map { "locationBeforeTransitions": Map { "pathname": "blank", "search": "", "hash": "", "state": null, "action": "POP", "key": "5b05pd", "query": Map {}, "$searchBase": Map { "search": "", "searchBase": "" } } } }
—— log end ——
<Provider> does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.
Warning: [react-router] You cannot change <Router history>; it will be ignored
My Root component looks as follows:
class Root extends React.Component<RootComponentProps, void> {
public render() {
const { store, history, routes } = this.props;
return (
<Provider store={store}>
<div>
<Router history={history}>
{routes}
</Router>
<DevTools />
</div>
</Provider>
);
}
}
I have implemented a custom routerReducer
to work with immutable, as explained in the react-router-redux docs:
const initialRouterReducerState = Immutable.fromJS({
locationBeforeTransitions: null
});
let routerReducer = (state = initialRouterReducerState, action: any) => {
if (action.type === LOCATION_CHANGE) {
return state.merge({
locationBeforeTransitions: action.payload
});
}
return state;
};
This is my dev configureStore:
function configureStore(middlewares: Redux.Middleware[], rootReducer: Redux.Reducer, initialState: any): Redux.Store {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middlewares),
DevTools.instrument()
)
);
return store;
}
And my initial rendering:
// ...
const store = configureStore(middlewares, rootReducer, immutableInitialState);
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: (state: any) => state.get("routing").toJS()
});
// Render Root coponent
render(
<Root store={store} history={history} routes={routes} />,
document.getElementById(container)
);
I've been trying all evening multiple things but I have not been able to figure out what is the problem 😞
Have you had a chance to look at the source of <Provider>
? It’s smaller than your example code 😉 .
The line in question executes when <Provider>
receives a different store
instance as a prop.
This likely means
const store = configureStore(middlewares, rootReducer, immutableInitialState);
executes not once, as you probably intend, but multiple times, so two stores are created rather than one. You may set a debugger
statement there and see why it happens. I hope this helps!
Thanks! I have found the issue, I was running my unit tests sharing some stubs and the same instances were shared between multiple unit tests.
👍
executes not once, as you probably intend, but multiple times, so two stores are created rather than one.
I am really not understanding how this is happening. Has there been any recent updates on this matter that may be helpful, because I have been tinkering the heck out of this and am not getting anywhere.
Thanks in advance - I know this is probably a burned out topic you've covered too many times.
Here's my source:
configureStore.js
...
const enhancer = composeEnhancers(
applyMiddleware(thunk, router, logger),
autoRehydrate()
);
export default function configureStore(initialState: Object | void) {
const store = createStore(rootReducer, initialState, enhancer);
persistStore(store, {storage: localForage}).purge()
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers/index').default();
store.replaceReducer(nextRootReducer);
});
}
return store;
}
Root.js
...
import configureStore from '../store/configureStore';
const store = configureStore();
const history = syncHistoryWithStore(hashHistory, store);
export default () => (
<Connector horizon={horizon}>
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>
</Connector>
);
@ddaaggeett : you probably need to move your const store = configureStore()
out of Root.js
, and into your application's entry point file. What's probably happening is that Root.js
is getting hot reloaded, and it's generating a new store instance in the process.
I've got a working example of using the plain HMR API in my blog post Practical Redux, Part 3: Project Setup.
Here's the entry point of my application:
entry.js
...
import Root from './containers/Root';
const rootElement = document.getElementById('root');
if (module.hot) {
module.hot.accept();
}
ReactDOM.render(
<AppContainer>
<Root />
</AppContainer>,
rootElement
);
if (module.hot) {
module.hot.accept('./containers/Root', () => {
const RootEle = require('./containers/Root').default;
ReactDOM.render(
<AppContainer>
<RootEle />
</AppContainer>,
rootElement
);
});
}
Are you saying, place <Provider store={store} />
from Root.js just inside the <AppContainer />
of entry.js, like so:
...
<AppContainer>
<Provider store={store} >
<Root />
</Provider>
</AppContainer>
...
@ddaaggeett : along that line, yeah. The key is to not call configureStore()
inside of Root
, which also generally means not rendering Provider
in there either. See https://github.com/markerikson/project-minimek/blob/master/src/index.js for an example of how I do it in my sample application.
@markerikson - thanks for the examples
I see how the store is to be rendered a single time in the higher component.
entry.js:
...
ReactDOM.render(
<AppContainer>
<Connector horizon={horizon}>
<Provider store={store}>
<Root />
</Provider>
</Connector>
</AppContainer>,
rootElement
);
but now, how is the store
used to create history
which is fed to { Router } from 'react-router'
?
Root.js:
...
export default ({store}) => {
const history = syncHistoryWithStore(hashHistory, {store});
return (
<Router history={history} routes={routes} />
)
}
this is obviously incorrect and results in the error:
Uncaught TypeError: t.getState is not a function
or, without passing { store }
as an argument:
Uncaught ReferenceError: store is not defined
Ah... I don't use routing myself, but I'm pretty sure you don't want to call that syncHistoryWithStore()
inside of a component's render()
method. That should also be done just once, as part of the app initialization process.
i've posted a more detailed cover here
For resolving this error you need put store = createStore(reducers)
on top level of your containers Now your hot-loader triggers creating store
Here is example.
Also <Devtools />
can be reason of problem, remove it for testing
what about singleton?
const configureStore = (function () {
let store
return {
create () {
if (!store) {
store = createStore()
}
return store
}
}
})()
const store = configureStore.create()