I want to know if the user is leaving the page
I tried :
componentWillMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
But I get :
this.props.router is undefined
I saw some solutions, but I don't want to use this react way :
React.createClass
Version of react-router :
"react-dom": "^16.3.1",
"react-router-dom": "^4.2.2",
And how I implemented the router :
root.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import { App } from '../components/app'
import { BrowserRouter } from 'react-router-dom'
// Render component with data
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('app');
ReactDOM.render(
(<BrowserRouter>
<App />
</BrowserRouter>),
node )
});
app.jsx
import React from 'react'
import CompanyList from './company/company_list'
import CompanyDetails from './company/company_details'
import { CompanyNew } from "./company/company_new";
import { Error404 } from "./common/error_404";
import { Route, Switch } from 'react-router-dom'
export const App = () => {
return (
<Switch>
<Route path='/' exact component={CompanyList}/>
<Route path='/companies' component={CompanyList}/>
<Route path='/company/new' component={CompanyNew}/>
<Route path='/company/:id' component={CompanyDetails}/>
/* 404 if not found : */
<Route component={Error404}/>
</Switch>
)
};
EDIT :
I tried with :
import { withRouter } from 'react-router'
class CompanyDetails extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
}
}
export default withRouter(CompanyDetails)
But I get the same error
You can get history off context or create it outside the Router and pass it in as a history prop.
Related
I am trying to use React-Router V4 to add routes to my app , I'm trying to
programatically change the route with history.push(), which is updating the
browser URL , but route still match the old URL.
NOTE: I am using redux.
The only answered question on this issue is:
wrap any redux connected component that has router components inside
it with a withRouter().
However, I've tried the answer to the above question, and it doesn't work for me.
Here are the important snippets:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import Routes from './Routes.js';
import store from './Store.js';
import {Provider} from 'react-redux';
import './css/bootstrap.min.css';
import './css/navbar/chartist-custom.css';
import './css/navbar/main.css';
import './css/navbar/font-awesome.min.css';
import './css/navbar/style.css';
import {createBrowserHistory} from 'history'
const history = createBrowserHistory();
const App = () => {
return (<Provider store={store}>
<Routes history={history}/></Provider>);
}
ReactDOM.render(<App/>, document.getElementById('root'));
registerServiceWorker();
Routes.js
import React, {Component} from 'react';
import {
Route,
Switch,
Link,
BrowserRouter,
Router,
Redirect
} from 'react-router-dom';
import LoginPage from './views/pages/LoginPage.js';
import SuccessPage from './views/pages/SuccessPage.js';
import errorPage from './views/pages/errorPage.js';
import store from './Store.js';
class Routes extends Component {
constructor(props) {
super(props);
this.URLChange = this.URLChange.bind(this);
this.getOwnState = this.getOwnState.bind(this);
this.state = this.getOwnState();
}
getOwnState() {
return {
currentURL: store.getState()["currentURL"]
};
}
URLChange() {
console.debug(this.getOwnState()["currentURL"]);
this.props.history.push(this.getOwnState()["currentURL"]);
//setState是异步的
let currentURL = this.getOwnState()["currentURL"];
this.setState(Object.assign({
currentURL
}, {currentURL}), () => {
//回调方法
console.debug("1:" + this.state.currentURL)
})
}
componentDidMount() {
store.subscribe(this.URLChange);
}
render() {
alert("render:" + JSON.stringify(this.props.history.location.pathname));
return (<BrowserRouter >
<Switch>
<Route exact="exact" path="/" component={errorPage}/>
<Route exact="exact" path="/LoginPage" component={LoginPage}/>
<Route exact="exact" path="/SuccessPage" component={SuccessPage}/>
<Route exact="exact" path="/errorPage" component={errorPage}/>
<Route exact="exact" path="/*" component={errorPage}/>
</Switch>
</BrowserRouter>);
}
}
export default Routes;
LoginPage.js:
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginPage));
SuccessPage.js:
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SuccessPage));
And I find that render returns the Route is
<Route path="/LoginPage" component={LoginPage}/>
rather than
<Route path="/SuccessPage" component={SuccessPage}/>
but use <Link> can change view:
<Link to="/SuccessPage">SuccessPage</Link>
Since you are using this.props.history.push in Routes component, you need to wrap this component with withRouter
class Routes extends Component {
constructor(props) {
super(props);
this.URLChange = this.URLChange.bind(this);
this.getOwnState = this.getOwnState.bind(this);
this.state = this.getOwnState();
}
getOwnState() {
return {
currentURL: store.getState()["currentURL"]
};
}
URLChange() {
console.debug(this.getOwnState()["currentURL"]);
this.props.history.push(this.getOwnState()["currentURL"]);
//setState是异步的
let currentURL = this.getOwnState()["currentURL"];
this.setState(Object.assign({
currentURL
}, {currentURL}), () => {
//回调方法
console.debug("1:" + this.state.currentURL)
})
}
componentDidMount() {
store.subscribe(this.URLChange);
}
render() {
alert("render:" + JSON.stringify(this.props.history.location.pathname));
return (<BrowserRouter >
<Switch>
<Route path="/LoginPage" component={LoginPage}/>
<Route path="/SuccessPage" component={SuccessPage}/>
<Route path="/errorPage" component={errorPage}/>
<Route path="/*" component={errorPage}/>
</Switch>
</BrowserRouter>);
}
}
export default withRouter(Routes);
You need to add the 'exact' keyword in your main route. Like:
<Route exact path="/LoginPage" component={LoginPage}/>
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import Routes from './Routes.js';
import store from './Store.js';
import {Provider} from 'react-redux';
import './css/bootstrap.min.css';
import './css/navbar/chartist-custom.css';
import './css/navbar/main.css';
import './css/navbar/font-awesome.min.css';
import './css/navbar/style.css';
import {BrowserRouter} from 'react-router-dom';
const App = () => {
return (<Provider store={store}>
<BrowserRouter><Routes/></BrowserRouter>
</Provider>);
}
ReactDOM.render(<App/>, document.getElementById('root'));
registerServiceWorker();
Routers.js
import React, {Component} from 'react';
import {
Route,
Switch,
Link,
BrowserRouter,
Router,
Redirect,
withRouter
} from 'react-router-dom';
import LoginPage from './views/pages/LoginPage.js';
import SuccessPage from './views/pages/SuccessPage.js';
import errorPage from './views/pages/errorPage.js';
import store from './Store.js';
import {Provider} from 'react-redux';
import PropTypes from 'prop-types'
class Routes extends Component {
constructor(props, context) {
super(props, context);
this.URLChange = this.URLChange.bind(this);
this.getOwnState = this.getOwnState.bind(this);
this.state = this.getOwnState();
}
static contextTypes = {
router: PropTypes.object
}
getOwnState() {
return {
currentURL: store.getState()["currentURL"]
};
}
URLChange() {
console.debug(this.getOwnState()["currentURL"]);
//setState是异步的
let currentURL = this.getOwnState()["currentURL"];
this.setState(Object.assign({
currentURL
}, {currentURL}), () => {
//回调方法
console.debug("回调方法执行完成this.state.currentURL:" + this.state.currentURL)
console.debug("旧的URL:" + this.context.router.history.location.pathname);
console.debug("新的URL:" + this.getOwnState()["currentURL"]);
//改变路由
this.context.router.history.push(this.getOwnState()["currentURL"]);
})
}
componentDidMount() {
store.subscribe(this.URLChange);
}
render() {
return (<div>
<Link to="/LoginPage">LoginPage</Link>
<br/>
<Link to="/SuccessPage">SuccessPage</Link>
<br/>
<Link to="/errorPage">errorPage</Link>
<br/>
<Switch>
<Route exact="exact" path="/" component={LoginPage}/>
<Route exact="exact" path="/LoginPage" component={LoginPage}/>
<Route exact="exact" path="/SuccessPage" component={SuccessPage}/>
<Route exact="exact" path="/errorPage" component={errorPage}/>
<Route exact="exact" path="/*" component={errorPage}/>
</Switch>
</div>);
}
componentWillUpdate() {}
componentDIdUpdate() {}
}
export default withRouter(Routes);
Unfortunately, I can't get React Router to work in my custom meteor boilerplate and I really can't figure out why. Here's all the files that could potentially be relevant to the problem:
\client\main.js:
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { renderRoutes } from '../imports/startup/client/routes.jsx';
Meteor.startup(() => {
render(renderRoutes(), document.getElementById('app'));
});
\imports\startup\client\routes.jsx:
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
// route components
import App from '../../ui/App.jsx';
export const renderRoutes = () => (
<Router>
<div>
<Route path="/" component={App} />
</div>
</Router>
);
\imports\ui\App.jsx
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>Hey!</h1>
);
}
}
export default withTracker(() => {
return {
};
})(App);
Any idea why the error message might occur? Thanks!
Not sure what version of meteor and react you are using but tha's how i did it on the last project i had.
Try this changes:
import {Router, Route, browserHistory} from 'react-router';
export const renderRoutes = () => (
<Router history={browserHistory}>
<div>
<Route path="/" component={App} />
</div>
</Router>
);
I am trying to render a specific component inside of another component based on React, React-Router v4, and Redux in my main 'panel' wrapped in a fixed header and sidebar component.
For example when I select an item from the sidebar, I to render the Detail panel and and load the details based on the id, like: <Route path='/item/:id' component={ItemDetail} />
routes.js
import React, { Component } from 'react';
import { RouteHandler, Switch, Route, DefaultRoute } from 'react-router';
import App from './containers/App';
import Login from './containers/Login';
import LobbyDetail from './components/LobbyDetail';
export default (
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/login" component={Login} />
</Switch>
);
app.js:
import React, { Component } from 'react'
import { Router, Route, Link } from 'react-router'
import { connect } from 'react-redux'
import PropTypes from 'prop-types';
import auth from '../actions/auth';
import Sidebar from '../Components/Sidebar'
class App extends Component {
static propTypes = {
};
/**
*
*/
render() {
const { ... } = this.props
return (
<div className="container-fluid">
<div className="row">
{* I WANT TO RENDER DYNAMIC COMPONENT HERE *}
</div>
<Sidebar currentUser={currentUser}
logout={logout}
/>
</div>
);
}
}
// ...
export default connect(mapStateToProps, mapDispatchToProps)(App)
index.js (basically main app):
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import routes from './routes';
import configureStore from './store/store.js';
import { AppContainer } from 'react-hot-loader';
const syncHistoryWithStore = (store, history) => {
const { routing } = store.getState();
if (routing && routing.location) {
history.replace(routing.location);
}
};
const initialState = {};
const routerHistory = createMemoryHistory();
const store = configureStore(initialState, routerHistory);
syncHistoryWithStore(store, routerHistory);
const rootElement = document.querySelector(document.currentScript.getAttribute('data-container'));
const render = () => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={routerHistory}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>,
rootElement
);
}
render();
if (module.hot) { module.hot.accept(render); }
What you're looking for is parameterized routing. Make a <Route/> like the following: <Route path='/item/:id' component={ MyComponent } />.
Now in MyComponent you can use the value of props.match.params.id to conditionally render, or if you're trying to load async data based on the value of :id; You can use the componentWillReceiveProps life cycle method and dispatch an action based on the value of this.props.match.params.id.
Note: <Link to='/item/some-item'/> will set the value of match.params.id to 'some-item'.
I'm using the latest ASP.NET 2.0 react SPA template, which is created with the command dotnet new react. It works great out of the box, with hot module replacement (code edits update in browser automatically).
Then I wanted to add a top level app container, called App to hold application state, as described in this blog post:
react without redux article. Should be simple, right? Note that the article uses javascript, but I'm using Typescript, since that's how the template was designed. But after adding a simple top level container component and updating the hot loader config, it doesn't work. I get an error that the component doesn't know how to reload itself. This is the code that is working (from the template):
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<BrowserRouter children={ routes } basename={ baseUrl } />
</AppContainer>,
document.getElementById('react-app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}
And this is the code after my changes, that is NOT working:
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
import App from './components/App';
import * as AppModule from './components/App'
let routes = RoutesModule.routes;
const render = (Component: any) => {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
//const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('react-app')
);
}
render(App);
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./components/App', () => {
const nextRoot = (require('./components/App') as typeof AppModule).default;
render(nextRoot);
});
}
For reference, here's the ./routes file (I did not change this file):
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>;
And here's my new App container component:
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from '../routes';
import { ReactElement } from "react";
let routes = RoutesModule.routes;
interface AppState {
stateDescription: string;
}
export default class App extends React.Component<{}, {}> {
constructor(props: any) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
return (
<BrowserRouter children = { routes } basename = { baseUrl } />
);
}
}
Any ideas or suggestions would be much appreciated!
I was able to resolve this by reverting my boot.tsx file to the original template version, and placing my App component in the routes.tsx file instead. You can see I just wrapped everything in the new App element. Here's the new routes.tsx file:
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { App } from './components/App';
export const routes = <App>
<Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>
</App>;
And here's my modified App component that works with this version:
export class App extends React.Component<AppProps, {}> {
constructor(props: AppProps) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
return (
<div className='appContainer'>
{ this.props.children }
</div>
);
}
}
I am trying to implement a navigation example using react router and stuck in an error. Please find the code
app.js
-------
import React from 'react';
import ReactDOM from 'react-dom';
import Routes from './routes';
ReactDOM.render(Routes, document.getElementById('react-container'));
routes.js
---------
import { React,Component } from 'react';
import { DefaultRoute, Link, Route, RouteHandler,Router } from 'react-router';
import Page1 from './page1';
import Home from './home';
export default class Routes extends Component {
constructor() {
super();
}
render() {
return (
<Router >
<Route path="/" component={Home}>
<Route name="page1" path="/1" component={ Page1 }/>
</Route>
</Router>
);
}
}
home.js
-------
import React, { Component } from 'react';
export default class Home extends Component {
constructor() {
super();
}
render() {
return (
<div>
<Header />
{this.props.children}
</div>
);
}
}
page1.js
--------
import React, { Component } from 'react';
export default class Page1 extends Component {
constructor() {
super();
}
render() {
return (
<div>
<h1> Page 1 </h1>
</div>
);
}
}
I have babel transformer to convert from es6 to es5 and I get the following error on loading the app,
Uncaught Invariant Violation: ReactDOM.render(): Invalid component element. Instead of passing a component class, make sure to instantiate it by passing it to React.createElement.
Can anyone help me in troubleshooting this issue ?
The problem is in your routes.js. You are wrapping your routes inside of a component, then passing that to ReactDOM.render. You should be passing the jsx directly. My suggestions would be to unwrap routes from your class, and just export the jsx.
routes.js
---------
import React from 'react'
import { Router, Route } from 'react-router'
import Page1 from './page1'
import Home from './home'
let routes =
<Router>
<Route path="/" component={Home}>
<Route name="page1" path="/1" component={Page1}/>
</Route>
</Router>
export default routes
The error is complaining that you are passing a class, rather then calling React.createElement. Remember that this:
let someElement = <div>Hello</div>
Will turn into this:
var someElement = React.createElement(
"div",
null,
"Hello"
)