React router 4 nesting routes alternative technique - javascript

On my attempt to do nested routes, I've failed to have the child components to mount when the route changes through Link or history.push; but if declaring the routes directly in the root.js file, it works. So, ideally I'd like to keep as much routes configuration as possible in the root/routes.js file and not all over the App (I'm iterating over the root/routes.js object instead to do this automatically; I mean... trying)
To break it down logically (it's a bit abstract, but check the code below afterwards please):
- There's a `root/routes.js` that has all the routes configuration (parents, nested components, etc)
- The `root.js` defines the `<Route...>` where the attribute `component` is the return value of a function that passes the `routes` configuration to its `routes` component prop
- the main wrapper iterates over the component prop `routes` and defines `child` routes automatically...I mean...I'm trying...
Why would I want to do this? The way my brain works and why not? Was possible before react router 4
<MyAppWrapper>
<CommonNavigationBar />
<Main>
----- dynamic / changes by route etc -----
</Main>
<Footer />
</MyAppWrapper>
I wonder where my attempt is failing?
// Working version
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
import App from '../app/containers/app'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' component={App} />
</BrowserRouter>
</Provider>
)
}
export default Root
For the previous example, the App component as nested , bellow I'm trying to do that dynamically..and it fails for some reason! It should be exactly the same though...there must be a typoe somewhere...
like,
import React, { Component } from 'react'
import { isBrowser } from 'reactatouille'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { withRouter, Route } from 'react-router'
import Navbar from '../navbar'
import JourneySelector from '../journeySelector'
import reservationFinder from '../../../reservationFinder'
// include the stylesheet entry-point
isBrowser() && require('../../../../sass/app.scss')
class App extends Component {
constructor (props) {
super(props)
this.state = {
init: true
}
}
render () {
return (
<div className={'app' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
<Route exact path='/' component={JourneySelector} />
<Route exact path='/reservation-finder' component={reservationFinder.containers.App} />
</main>
</div>
)
}
}
// export default App
function mapStateToProps (state, ownProps) {
return {
// example: state.example
}
}
function matchDispatchToProps (dispatch) {
return bindActionCreators({
// replay: replay
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(withRouter(App))
While my technique fails (all I'm trying to do is iterate the root/routes children routes to generate these ):
// root/routes.js
import app from '../app'
import reservationFinder from '../reservationFinder'
const rootRoutes = [
{
path: '/',
component: app.containers.App,
exact: true,
routes: [{
path: '/',
exact: true,
component: app.containers.JourneySelector
}, {
path: '/reservation-finder',
component: reservationFinder.containers.App
}]
}
]
export default rootRoutes
The root js file. You see the setRoute fn returns a new component, where the children routes is passed as a props? I believed this would work:
// root.js
import React from 'react'
import { Route, Switch } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} exact={route.exact || false} component={() => (<MyComponent routes={route.routes} />)} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
{ rootRoutes.map(route => setRoute(route)) }
</BrowserRouter>
</Provider>
)
}
export default Root
the main app that I want to use as a wrapper:
// main app
import React, { Component } from 'react'
import { isBrowser } from 'reactatouille'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { withRouter, Route } from 'react-router'
import Navbar from '../navbar'
// include the stylesheet entry-point
isBrowser() && require('../../../../sass/app.scss')
class App extends Component {
constructor (props) {
super(props)
this.state = {
init: true
}
}
render () {
return (
<div className={'app' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
{ Array.isArray(this.props.routes) && this.props.routes.map(route => <Route key={route.path} {...route} />) }
</main>
</div>
)
}
}
// export default App
function mapStateToProps (state, ownProps) {
return {
// example: state.example
}
}
function matchDispatchToProps (dispatch) {
return bindActionCreators({
// replay: replay
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(withRouter(App))
I understand I MIGHT be able to achieve something similar, like?!
// root
import React from 'react'
import { Route, Switch } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
import MyAppWrapper from 'xxx/MyAppWrapper'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} exact={route.exact || false} component={() => (<MyComponent routes={route.routes} />)} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<MyAppWrapper>
<Route path='x' component={x} />
<Route path='y' component={y} />
</MyAppWrapper>
</BrowserRouter>
</Provider>
)
}
export default Root
Notes: During testing, I've noticed that it worked server-side? I mean, I may have missed something, and I didn't save my work. Also, when it fails, the previous component (the default) is still mounted and does not unmount
I even tried (without sucess...I wonder if this is a bug):
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import App from '../app/containers/app'
import rootRoutes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' render={() => (
<App />
)} />
</BrowserRouter>
</Provider>
)
}
export default Root
Ok, I think this is a bug so reported ( https://github.com/ReactTraining/react-router/issues/5190 ), you can find the live example here ( https://codepen.io/helderoliveira/pen/rmXdgd ), click topic. Maybe what I'm trying to do is not supported, but instead of blank we should get an error message.

Ok, I found the typo. The solution is to use render and pass the routerProps + any other props you desire through Object.assign and the spread operator!
// root.js
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import App from '../app/containers/app'
import rootRoutes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<Route path='/' render={routeProps => <App {...Object.assign({}, routeProps, { routes: rootRoutes[0].routes })} />} />
</BrowserRouter>
</Provider>
)
}
export default Root
And the main app wrapper:
class App extends Component {
render () {
return (
<div className={'app kiosk' + ' ' + (!this.state.init && 'uninitialised')}>
<Navbar />
<main>
{ Array.isArray(this.props.routes) && this.props.routes.map(route => <Route key={route.path} exact={route.exact} path={route.path} component={route.component} />) }
</main>
</div>
)
}
}
export default App
the routes file:
import React from 'react'
import { Route } from 'react-router'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import rootRoutes from './routes'
const setRoute = (route) => {
const MyComponent = route.component
return <Route key={route.path} path={route.path} render={routeProps => <MyComponent {...Object.assign({}, routeProps, { routes: rootRoutes[0].routes })} />} />
}
const Root = ({store, history}) => {
return (
<Provider store={store}>
<BrowserRouter history={history}>
<div>
{ rootRoutes.map(route => setRoute(route)) }
</div>
</BrowserRouter>
</Provider>
)
}
export default Root

Related

The code from react-router-dom of the old version does not work on react-router-dom of the new version

I have a consts.js file
export const ADMIN_ROUTE = '/admin'
export const LOGIN_ROUTE = '/login'
export const REGISTRATION_ROUTE = '/registration'
export const SHOP_ROUTE = '/'
export const BASKET_ROUTE = '/basket'
export const DEVICE_ROUTE = '/product'
I have a routes.js file
import Admin from "./pages/Admin"
import Auth from "./pages/Auth"
import Basket from "./pages/Basket"
import DevicePage from "./pages/DevicePage"
import Shop from "./pages/Shop"
import {
ADMIN_ROUTE,
BASKET_ROUTE,
DEVICE_ROUTE,
LOGIN_ROUTE,
REGISTRATION_ROUTE,
SHOP_ROUTE
} from "./utils/consts"
export const authRoutes = [
{
path: ADMIN_ROUTE,
Component: Admin
},
{
path: BASKET_ROUTE,
Component: Basket
}
]
export const publicRoutes = [
{
path: SHOP_ROUTE,
Component: Shop
},
{
path: LOGIN_ROUTE,
Component: Auth
},
{
path: REGISTRATION_ROUTE,
Component: Auth
},
{
path: DEVICE_ROUTE + '/:id',
Component: DevicePage
},
]
App.js
import { BrowserRouter } from "react-router-dom";
import AppRouter from "./components/AppRouter";
function App() {
return (
<BrowserRouter>
<AppRouter/>
</BrowserRouter>
);
}
export default App;
AppRouter.js
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import { authRoutes, publicRoutes } from '../routes';
const AppRouter = () => {
const isAuth = false;
return (
<Switch>
{isAuth && authRoutes.map(({path, Component}) =>
<Route key={path} path={path} component={Component} exact />
)}
{publicRoutes.map(({path, Component}) =>
<Route key={path} path={path} component={Component} exact />
)}
</Switch>
);
};
export default AppRouter;
I get the error, export 'Switch' (imported as 'Switch') was not found in 'react-router-dom'. I tried all variants, tried to replace Switch with Router and so on, but nothing helps. I know they removed Switch in react-router-dom#6.0, but I don't understand how to fix this code to make it work.
The Switch component was replaced by the Routes component in react-router-dom#6. Additionally the Route component API changed as well, instead of the component and render and children function props there is now a single element prop taking a React.ReactNode, a.k.a JSX, value.
Example:
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { authRoutes, publicRoutes } from '../routes';
const AppRouter = () => {
const isAuth = false;
return (
<Routes> // <-- Directly wrap all Route components
{isAuth && authRoutes.map(({path, Component}) => (
<Route
key={path}
path={path}
element={<Component />} // <-- ReactNode/JSX
/>
))}
{publicRoutes.map(({path, Component}) => (
<Route
key={path}
path={path}
element={<Component />} // <-- ReactNode/JSX
/>
))}
</Routes>
);
};
See the Migrate from v5 guide for more details on all the breaking changes from v5 to v6.

Route Component renders multiple times when route is split into multiple files

The issue is that whenever I try to migrate my routes into another file and import them into parent file(routes.js) then whenever I tried to navigate to those migrated route the whole layout is re-rendered and if I keep migrated routes in the parent route component then the layout is rendered once as expected.
The route component where all the routes are written as below
routes.js
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import MainLayout from '../pages/layouts/MainLayout';
import Login from '../components/Login/Login';
import UserList from "../components/User/UserList";
import UserCreate from '../components/User/UserCreate';
import UserEdit from '../components/User/UserEdit';
import UserSetting from '../components/User/UserSetting';
class Main extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={Login}/>
<PrivateRoute exact path='/user' component={UserList} layout={MainLayout}/>
<PrivateRoute exact path='/user/create' component={UserCreate} layout={MainLayout}/>
<PrivateRoute exact path='/user/edit/:id' component={UserEdit} layout={MainLayout}/>
<PrivateRoute exact path='/user/setting/:id' component={UserSetting} layout={MainLayout}/>
</Switch>
)
}
}
export default Main;
UserRoutes.js
import UserList from "../components/User/UserList";
import UserCreate from '../components/User/UserCreate';
import UserEdit from '../components/User/UserEdit';
import UserSetting from '../components/User/UserSetting';
const UserRoutes = [
{
path : '/user',
component : UserList,
},
{
path : '/user/create',
component : UserCreate,
},
{
path : '/user/edit/:id',
component : UserEdit,
},
{
path : '/user/setting/:id',
component : UserSetting,
},
];
export default UserRoutes;
Now, when I try to import this route in place of the written routes in routes.js component, the above mentioned issue arises.
routes.js
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import MainLayout from '../pages/layouts/MainLayout';
import Login from '../components/Login/Login';
import UserRoutes from './UserRoutes';
class Main extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={Login}/>
{
UserRoutes.map(({ path, component }, key) =>
<PrivateRoute exact layout={MainLayout} path={path} component={component} key={key} />
)
}
</Switch>
)
}
}
export default Main;
PrivateRoute.js
import React from 'react';
import {Redirect, Route, withRouter} from 'react-router-dom';
let state_of_state = localStorage["appState"];
let Auth = {};
if (!state_of_state){
localStorage["appState"] = JSON.stringify({user : {}});
} else {
Auth = JSON.parse(state_of_state);
}
const PrivateRoute = ({ layout: Layout, component: Component, ...rest }) => (
<Route {...rest} render={props => {
const newComponent = Layout ?
<Layout><Component {...rest} {...props} /></Layout>
:
<Component {...props} />;
return (!_.isEmpty(Auth.user)) ? (
newComponent
) : (
<Redirect to={{pathname: '/', state: { from: props.location }}} />
)
}}/>
);
export default PrivateRoute;
I need some guidance where I am going wrong with this.

mobx #inject not creating new (decorated) component

I'm trying to setup a React application using mobx for state management.
I've defined a store like so:
ApplicationStore.js
import { observable, action, reaction } from 'mobx';
class ApplicationStore {
#observable appName = 'App';
#observable currentRoute = '/';
#observable appLoaded = false;
#action setAppLoaded() {
this.appLoaded = true;
}
}
export default new ApplicationStore();
Using it in Root.js to collect stores/debug tools and passing it to main App component:
Root.js
import React, { Component } from 'react';
import { Provider } from 'mobx-react';
import App from './App.js';
import './Root.css';
import ApplicationStore from './stores/ApplicationStore';
const stores = { ApplicationStore }
class Root extends Component {
render() {
return (
<Provider {...stores}>
<App />
</Provider>
);
}
}
export default Root;
App.js
import React, { Component } from 'react';
import Login from './views/login/Login'
import Dashboard from './views/dashboard/Dashboard'
import { observer, inject } from 'mobx-react';
import { Switch, Route, BrowserRouter, Redirect } from 'react-router-dom'
#inject('ApplicationStore')
#observer
class App extends Component {
componentDidMount() {
console.log(this.props)
};
render() {
var loggedIn = false;
return (
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/dashboard" />
) : (
<Redirect to="/login" />
)
)} />
<Route exact path="/login" component={Login} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
The console.log(this.props) in componentDidMount() shows that there are empty props.
Not only that, inspecting <Provider>'s children in React-dev-tools shows that #inject isn't creating a new component (it shows <App> instead of the expected <inject-App-with-ApplicationStore>.
I don't understaaaaaaaaand
The issue was with the babel config. I'm using the react-app-rewired approach to adding decorator functionality to create-react-app which can be seen here...
In the config-overrides.js file I had:
const { injectBabelPlugin } = require("react-app-rewired");
module.exports = function override(config, env) {
config = injectBabelPlugin(['#babel/plugin-proposal-decorators', {decoratorsBeforeExport: false}], config);
return config;
}
I changed it to:
...
config = injectBabelPlugin(['#babel/plugin-proposal-decorators', {"legacy": true}], config);
...
and it's now working as intended.

How to redirect to a different page after authenticated with React

I have bought a template in envato which is quite complex, I know a bit of react, I am not an expert yet, however I managed to integrate Azure AD Authentication into the app.
I used this procedure:
https://github.com/salvoravida/react-adal
My app is like this:
DashApp.js
import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";
const currentAppLocale =
AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];
const DashApp = () => (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<DashAppHolder>
<Provider store={store}>
<PublicRoutes history={history} />
</Provider>
</DashAppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
Boot()
.then(() => DashApp())
.catch(error => console.error(error));
export default DashApp;
export { AppLocale };
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
and router.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import { connect } from 'react-redux';
import App from './containers/App/App';
import asyncComponent from './helpers/AsyncFunc';
const RestrictedRoute = ({ component: Component, isLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>}
/>
);
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};
export default connect(state => ({
isLoggedIn: state.Auth.get('idToken') !== null,
}))(PublicRoutes);
The authentication is working fine, but its redirected to a custom login page the template has, I want the redirect to be to /dashboard instead.
Thats the question, should be easy, but new to react routing
Please Give a try. (I'm not 100% sure - I just made a guess from the given code)
router.js
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
render={() => <Redirect to="/dashboard" />}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};

react not found component always renders

This is code for looping through routes file. Here i am creating routerconfig array an exporting from here to app.js.
import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import routes from './routes';
class RouterConfig extends Component {
render() {
return (
routes.map((route) => {
return (
<Route
key={ route.id }
path={ route.path }
exact={ route.exact }
component={ route.component }
/>
);
})
);
}
}
export default RouterConfig;
This is my route configuration file, where i have listed all routes.
import Home from '../components/home/home';
import About from '../components/about/about';
import Posts from '../components/posts/posts';
import NotFound from '../components/not_found/not_found';
const routes = [
{
path: '/',
exact: true,
component: Home,
id: 'home',
},
{
path: '/about',
component: About,
id: 'about',
},
{
path: '/posts',
component: Posts,
id: 'posts',
},
{
component: NotFound,
id: 'not_found',
},
];
export default routes;
This is my app.js
import React, { Component } from 'react';
import { Switch, Router, Route } from 'react-router-dom';
import Header from '../header/header';
import Footer from '../footer/footer';
import history from '../../history';
import RouterConfig from '../../router/browserroute';
class App extends Component {
render() {
return (
<div>
<Router history={ history } >
<div>
<Header />
<Switch>
<RouterConfig />
</Switch>
<Footer />
</div>
</Router>
</div>
);
}
}
export default App;
issue is on every page i am getting not found component render. Even
using switch with routes not getting the expected results. not sure
why code is not working properly.
[this is how it renders not found in every page ][1]
[1]: https://i.stack.imgur.com/B7evW.png
on changing:
class App extends Component {
render() {
return (
<div>
<Router history={ history } >
<div>
<Header />
<Switch>
<Route exact path='/' component={ Home } />
<Route path='/about' component={ About } />
<Route path='/posts' component={ Posts } />
<Route component={ NotFound } />
</Switch>
<Footer />
</div>
</Router>
</div>
);
}
}
This works fine
You need to move Switch to be inside RouteConfig. There must be something with the way React 16 partials work that is unexpected in React Router (I'm not really sure why it does not work when Switch is outside RouterConfig). The good news is, at least for me, it makes sense to be inside RouteConfig.
Here is a codesandbox that works:
https://codesandbox.io/s/8xmx478kq8
Which version of React are you using? If you are using React 15 or lower, it does not support returning an array inside render. You would need to wrap you stuff in a container.
import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import routes from './routes';
class RouterConfig extends Component {
render() {
return (
<div>{
routes.map((route) => {
return (
<Route
key={ route.id }
path={ route.path }
exact={ route.exact }
component={ route.component }
/>
);
})
}</div>
);
}
}
export default RouterConfig;
Notice the div wrapping the Routes array

Categories

Resources