maybe this issue is because the new version of the react router have few days, but I have been reading about this issue and I want to clarify what is going on. I am using the last version of the react router and I want to do the routing through redux. I follow the steps that are listed in the documentation of the redux router module: https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux, but I receive this error when I make the implementation: (I know that the issue is in the server render)
Invariant Violation: Browser history needs a DOM
Here is my code (the important parts):
server.js
import { Provider } from 'react-redux';
import store from './store';
lisaApp.get('*', function (req, res) {
const context = {};
const html = renderToString(
<Provider store={store}>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<StaticRouter location={req.url} context={context}>
<Routes />
</StaticRouter>
</MuiThemeProvider>
</Provider>,
);
res.setHeader('Content-Type', 'text/html');
if (context.url) {
res.writeHead(301, {
Location: context.url,
});
res.end();
}
res.write(
renderToStaticMarkup(<Layout title={req.title} content={html} />),
);
res.end();
}
client.js
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import { BrowserRouter } from 'react-router-dom';
import store from './store';
render((
<Provider store={store}>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<BrowserRouter history={createHistory()}>
<Routes />
</BrowserRouter>
</MuiThemeProvider>
</Provider>),
document.getElementById('app'));
store.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import createHistory from 'history/createBrowserHistory'
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
import thunk from 'redux-thunk';
import reducer from './reducer';
const history = createHistory()
const middlewareHistory = routerMiddleware(history)
const store = createStore(
combineReducers({
reducer,
router: routerReducer
}),
applyMiddleware(
middlewareHistory,
thunk
)
);
export default store;
component.js (dispatch)
const mapDispatchToProps = dispatch => {
return {
onNavigateTo(dest) {
dispatch(push(dest));
}
};
};
Obviously the dispatch, from my component never is called. Anyone can me clarify me where I am wrong? or maybe this feature is not implemented yet in the react router redux module? In advance Thanks.
Instead of BrowserRouter, use ConnectedRouter from the react-router-redux library:
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import { ConnectedRouter } from 'react-router-redux';
import store from './store';
render((
<Provider store={store}>
<MuiThemeProvider muiTheme={getMuiTheme()}>
<ConnectedRouter history={createHistory()}>
<Routes />
</ConnectedRouter>
</MuiThemeProvider>
</Provider>),
document.getElementById('app'));
I faced the same issue few days ago. In your BrowserRouter you manually create and pass a browserHistory object. Then, whenever you need access to the history object you import it, like it happens in your store.js, which is shared between the server and the client. However, on the server there is no DOM, hence the error.
I fixed the issue by NOT creating the history object manually, this is not needed since you use the BrowserRouter. As stated in the documentation, the BrowserRouter is:
A <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.
Instead of importing the history manually whenever you need it, just use the handy withRouter higher order component. This way, in your props you will get access not only to the history, but also to the closest <Routes>'s match. Read more about it here.
Regarding your last point about the dispatch, you are right that it's not called. Whenever you create a component via the connect of react-redux, remember to wrap it with the withRouter higher order component. Read more about it here.
I believe the above will fix your issue. I can share a working example if you want me to, but my solution is very similar to what you have, with the exception of my above comments.
I hope this helps.
This has worked for me.
I used ConnectRouter from the "react-router-dom" library. Along with it i used BrowserRouter from the "react-router-dom" library. **Now i am able to use React+Redux+Routing happily.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./reducers";
import App from "./App";
import { connectRouter } from "connected-react-router";
import createHistory from "history/createBrowserHistory";
export const history = createHistory();
const store = createStore(connectRouter(history)(rootReducer));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
I hope this will work for you as well.
Happy coding!!!
Related
I am trying to implement react-admin in my project however upon render I get this message
Uncaught Error: Missing history prop. When integrating react-admin inside an existing redux Provider, you must provide the same 'history' prop to the <Admin> as the one used to bootstrap your routerMiddleware. React-admin uses this history for its own ConnectedRouter.
There is very little to be found about this issue and I'm not entirely sure how to go about setting the history. I've tried importing createHistory from 'history/createHashHistory' but then I get this error
Uncaught Could not find router reducer in state tree, it must be mounted under "router"
Is there a way to get this rendering properly? And if so, what would be the proper way to go about configuring the Admin Component's history?
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore, compose, applyMiddleware} from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { BrowserRouter as Router } from 'react-router-dom';
import { reducer } from './redux/reducer';
import * as serviceWorker from './serviceWorker';
const store = createStore(reducer, compose(
applyMiddleware(thunk),
window.navigator.userAgent.includes('Chrome') ?
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() : compose,
),
);
ReactDOM.render(
<React.StrictMode>
<Router>
<Provider store={store}>
<App />
</Provider>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
AdminPage.js
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const dataProvider = jsonServerProvider('http://localhost:3000');
const AdminPage = () => {
return (
<Admin dataProvider={dataProvider}>
<Resource name="services" />
</Admin>
)
}
export default AdminPage;
App.js
import React from 'react';
import './App.css';
import AdminPage from './components/AdminPage';
import { Switch, Route } from 'react-router-dom';
const App = () => {
return (
<div className="app">
<Switch>
<Route exact path='/manage' component={ AdminPage } />
</Switch>
</div>
);
}
export default App;
The proper way to write a reducer in this case is documented at https://marmelab.com/react-admin/CustomApp.html#using-an-existing-redux-provider.
But from what I see, you don't need one. You just added the thunk middleware, which is useless in react-admin as it already handles sagas.
I am trying server side rendering using react-router 4. I am following the example provided here https://reacttraining.com/react-router/web/guides/server-rendering/putting-it-all-together
As per the example on server we should use StaticRouter. When I import as per the example I am seeing StaticRouter as undefined
import {StaticRouter} from 'react-router';
After doing some research online I found I could use react-router-dom. Now my import statement looks like this.
import {StaticRouter} from 'react-router-dom';
However when I run the code I am getting Invariant Violation: Browser history needs a DOM in the browser.
my server.js file code
....
app.get( '*', ( req, res ) => {
const html = fs.readFileSync(path.resolve(__dirname, '../index.html')).toString();
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context} >
<App/>
</StaticRouter>
);
if (context.url) {
res.writeHead(302, {
Location: context.url
})
res.end();
} else {
res.send(html.replace('$react', markup));
}
} );
....
And my client/index.js code
....
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), root);
....
Update v1
Reduced my example to a bear minimum and still getting the same error.
clientIndex.js
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from '../App'
ReactDOM.render((
<BrowserRouter>
<App/>
</BrowserRouter>
), document.getElementById('app'))
serverIndex.js
import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../App'
createServer((req, res) => {
const context = {}
const html = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
res.write(`
<!doctype html>
<div id="app">${html}</div>
`)
res.end()
}).listen(3000);
App.js
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import routes from "./client/routes";
const App = ( ) => (
<Router>
<Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
</Router>
)
export default App;
You need to use different history provider for server side rendering because you don't have a real DOM (and browser's history) on server. So replacing BrowserRouter with Router and an alternate history provider in your app.js can resolve the issue. Also you don't have to use two wrappers. You are using BrowserRouter twice, in app.js as well as clientIndex.js which is unnecessary.
import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
const history = createMemoryHistory();
<Router history={history}>
<Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
</Router>
You can now replace StaticRouter with ConnectedRouter which can be used both in client and server. I use the following code to choose between history and export it to be used in ConnectedRouter's history.
export default (url = '/') => {
// Create a history depending on the environment
const history = isServer
? createMemoryHistory({
initialEntries: [url]
})
: createBrowserHistory();
}
In clientIndex.js
Rather than BrowserRouter use StaticRouter.
import { BrowserRouter } from 'react-router-dom';
import { StaticRouter } from 'react-router-dom'
As is essentially noted in the comments, one may hit this error (as I have) by accidentally wrapping your App component in a <BrowserRouter>, when instead it is your client app that should be wrapped.
App.js
import React from 'react'
const App = () => <h1>Hello, World.</h1>
export default App
ClientApp.js
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom'
import App from './App'
const render = Component => {
ReactDOM.render(
<BrowserRouter>
<Component />
</BrowserRouter>,
document.getElementById('app')
)
}
render(App)
See also the React Router docs.
Hello I am working on Shopping List and my file looks like this:
import React, { Component } from 'react';
import AppNavbar from './components/AppNavbar';
import ShoppingList from './components/ShoppingList';
import {Provider} from 'react-redux';
import store from './';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
function App() {
return (
<Provider store={store}>
<div className="App">
<AppNavbar/>
<ShoppingList/>
</div>
</Provider>
);
}
export default App;
However, Ireceived this error:
Failed to compile
./src/App.js
Attempted import error: './' does not contain a default export (imported as 'store').
can someone please help fix this ?
The problem in your code is how you are trying load the store with import store from './';.
Instead you need to use createStore() from redux as the following:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(
/* your reducers */,
applyMiddleware( /* your middleware */ ),
);
What you can pass to the <Provider /> as follows:
ReactDOM.render(<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
Read further here about createStore(reducer, \[preloadedState\], \[enhancer\]) which:
Creates a Redux store that holds the complete state tree of your app. There should only be a single store in your app.
I hope this helps!
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
import Posts from './containers/Posts'
import NavBar from './components/NavBar';
// Apply Thunk middleware
const middleware = applyMiddleware(thunk);
// Create store
const store = createStore(reducers, enhancer);
class App extends Component {
render() {
return (
<Provider store={store}>
<React.Fragment>
<NavBar />
<Posts />
</React.Fragment>
</Provider>
);
As you can see, the app.js page should be looked like this. You can't just import store from another file. It's better to use best practices like this.
Now you will wonder how the reducers came from. Generally, developers put all the reducers in a reducers folder. In that folder you can have a index.js file and other reducer files. For now, you better use a empty main reducer index.js like below.
import { combineReducers } from 'redux'
const rootReducer = combineReducers({
null
})
export default rootReducer;
If you something like post or whatever, the code should be modified like this.
import { combineReducers } from 'redux'
import posts from './posts';
const rootReducer = combineReducers({
posts: posts
})
export default rootReducer;
Simply, posts is a post reducer in the reducers folder. Selecting a appropriate middleware is up to you. I have used redux-thunk. You can use redux-saga, redux-logic, or custom middleware. I think this will help you to solve your problem.
I am seeing duplicate renders when I click a Link or use the back button. I can go to any page and do a real browser refresh and everything will work properly. My logger shows ##router/LOCATION_CHANGE twice and this is causing lots of warning and exceptions around duplication instances of things. This does not appear to be an issue of hashHistory vs browserHistory. As people have pointed out on github issues. I am on "react-router-redux": "4.0.7". Setting adjustUrlOnReplay to false doesn't appear to do anything. As always, really appreciate any help! Below are my configureStore and Routes js files. Can anyone help me find the problem here? Thanks! Phillip
configureStore.js
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import rootReducer from '../reducers'
import createSagaMiddleware from 'redux-saga'
import rootSaga from '../sagas/sagas'
import promiseMiddleware from 'redux-promise-middleware'
import { syncHistoryWithStore} from 'react-router-redux'
import { browserHistory } from 'react-router'
import { apiMiddleware } from 'redux-api-middleware';
const sagaMiddleware = createSagaMiddleware()
const initialState = {
};
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(apiMiddleware, thunk, logger(), sagaMiddleware),
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
));
export const history = syncHistoryWithStore(browserHistory, store);
sagaMiddleware.run(rootSaga);
export default store;
routes.js
import App from './App'
import '...a bunch of different components'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import store, { history } from './store/configureStore.js'
const router = (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={TeamsContainer}/>
<Route path="teams" component={TeamsContainer} />
<Route path="teams/:teamId" component={TeamContainer} />
<Route path="teams/:teamId/add_member" component={AddMemberContainer} />
<Route path="teams/:teamId/team_members/:teamMemberId" component={TeamMemberContainer} />
</Route>
</Router>
</Provider>
)
if ( $('#app').length ) {
ReactDOM.render(router, document.getElementById('app'));
}
https://github.com/ReactTraining/history/issues/427
Updating to react-router v4 solve this.
I've been trying to setup react + react-router + redux + redux-simple-router with no success. Everything seems to work fine until I add redux-simple-router to the mix, after which I get
"[TypeError: Cannot read property 'listen' of undefined] even though I am passing in BrowserHistory Object.
Here's the code -
import React from 'react'
import ReactDOM from 'react-dom'
import { Route, Router, BrowserHistory } from 'react-router';
import HashHistory from 'react-router/lib/HashHistory';
import { compose, combineReducers, applyMiddleware, createStore } from 'redux'
import { Provider } from 'react-redux'
import { syncHistory, routeReducer } from 'redux-simple-router'
import * as reducers from 'reducers/reducers'
import Blank from 'routes/blank';
export default (withHistory, onUpdate) => {
const reducer = combineReducers(Object.assign({}, reducers, {
routing: routeReducer
}));
const reduxRouterMiddleware = syncHistory(BrowserHistory)
const createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware)(createStore)
const store = createStoreWithMiddleware(reducer)
return (
<Provider store={store}>
<Router history={BrowserHistory} onUpdate={onUpdate}>
<Route path='/' component={Blank} />
</Router>
</Provider>
);
};
Note: I just discovered I'm compiling react templates in a express server using renderToString(). Is that why its not defined?? What do i do?
Note 2: if I instead import BrowserHistory from 'react-router/lib/BrowserHistory' then I get
TypeError: history.listen is not a function;
I just had the same error. I got it by following the tutorial here: https://github.com/rackt/redux-simple-router/blob/e1df260888d1032d1e68c7694a06b457a4f0130f/README.md
Then I realised above it stood this message:
Note: This example uses react-router's 2.0 API, which is currently
released under version 2.0.0-rc5.
Upgrading my packages like this:
history ^1.17.0 → ^2.0.0-rc2
react-router ^1.0.3 → ^2.0.0-rc5
... resolved the error.