I'm new to react.
I want to add some security to my Async-Routes which I implemented in my routes.js:
import React from 'react';
import App from './app.jsx';
import Home from './components/home.jsx';
import {Router, Route, IndexRoute, hashHistory} from 'react-router';
function loadRoute(cb) {
return (module) => cb(null, module.default);
}
const routes = {
component: App,
childRoutes: [
{
path: "/",
component: Home
},
{
path: "/hello/:foo",
getComponent(location, cb) {
System.import('./components/hello.jsx')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
]
};
export default () => <Router history={hashHistory} routes={routes} />
As you can see the "/hello/:foo" route is async.
How can I restrict the access to this route (role-based) and redirect to somewhere else (e.g. login)?
I want to load the chunk only when it's needed.
Should I place the checking code into "getComponent()"?
Can it be done with "willTransitionTo()", will this function be executed before "getComponent()" and how should I implement it?
I would place the checking code into componentWillMount(), and in render() return the page component, or display/redirect to login.
If you have multiple page that need access restricted, I'd create a high level component order for each page component to check access before rendering.
Related
I am trying to use React Shepherd to create a walkthrough for my application.
I can't seem to find anything that explains how to switch routes within the tour. window.location.replace = "/someurl" refreshes the page and kills the tour completely. I am trying to achieve something along the lines of this
History.js
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
export default history;
steps.js
import hist from "./History";
const Steps = [
{
//...
when: {
hide: () => {
hist.push("/someurl");
},
},
},
//...
]
export default Steps;
App.js
import React from "react";
import { Router } from "react-router";
import { Route } from "react-router-dom";
//...
import Steps from "./Steps";
import hist from "./History";
import "shepherd.js/dist/css/shepherd.css";
const tourOptions = {
defaultStepOptions: {
cancelIcon: {
enabled: true,
},
classes: "shepherd-theme-custom",
},
useModalOverlay: true,
};
const App = () => {
return (
<Router history={hist}>
<Route exact path="/signin" component={SignIn} />
<ShepherdTour steps={Steps} tourOptions={tourOptions}>
<PrivateRoute exact path="/*" component={Main} />
</ShepherdTour>
</Router>
);
};
export default App;
When the steps' hide function is called, the url path is switched but the page is not rendered. I am wondering if I am using react-router wrong or is there a different way to go about this?
So this actually had nothing to do with React Shepherd at all, this was purely a React Router issue. I mistakenly nested two BrowserRouter's since it was also accidentally included in my Main component. Once removed the application navigates with custom history file just fine.
I am trying to use the react-redux-firebase app as a base for my app.
I'm trying to add static pages to the footer links (no state) and then render them as the app expects.
I haven't seen this approach to setting up a route index.js before.
The generator creates this index file (to which I have tried to add the About route):
import CoreLayout from '../layouts/CoreLayout'
import Home from './Home'
import LoginRoute from './Login'
import SignupRoute from './Signup'
import ProjectsRoute from './Projects'
import AccountRoute from './Account'
import NotFoundRoute from './NotFound'
import AboutPage from "./About"
/* Note: Instead of using JSX, we recommend using react-router
PlainRoute objects to build route definitions. */
export const createRoutes = store => ({
path: '/',
component: CoreLayout,
indexRoute: Home,
childRoutes: [
AccountRoute(store),
LoginRoute(store),
SignupRoute(store),
ProjectsRoute(store),
AboutPage,
// AsyncRoute(store) // async routes setup by passing store
// SyncRoute, // sync routes just need route object by itself
/* Place all Routes above here so NotFoundRoute can act as a 404 page */
NotFoundRoute(store)
]
})
/* Note: childRoutes can be chunked or otherwise loaded programmatically
using getChildRoutes with the following signature:
getChildRoutes (location, cb) {
require.ensure([], (require) => {
cb(null, [
// Remove imports!
require('./Counter').default(store)
])
})
}
However, this is not necessary for code-splitting! It simply provides
an API for async route definitions. Your code splitting should occur
inside the route `getComponent` function, since it is only invoked
when the route exists and matches.
*/
export default createRoutes
This app layout is that all the views seem to be setup under the routes folder. Following that approach, I made an AboutPage folder within src/routes/About/components. That folder has a AboutPage folder with a js page and in index, as well as separate index.js file (similar to the HomePage component provided with the generated app). The nested index has:
import AboutPage from './AboutPage'
export default AboutPage
The src/routes/About/index.js
import AboutPage from './components/AboutPage/index.js'
// Sync route definition
export default {
component: AboutPage
}
I don't get any errors when I save this, but when I start the server and click the link, I just get a 404 error.
How do I add routes to this app?
I have tried a million different variations on the import statement in the routes/index.js - many of which produce no error but do not actually render the page.
The full setup is src/routes/About/components/AboutPage/AboutPage.js
class About extends React.Component {
componentDidMount() {
window.scrollTo(0, 0);
document.body.scrollTop = 0;
}
render() {
const { classes, ...rest } = this.props;
const imageClasses = classNames(
classes.imgCard,
classes.imgFluid
);
const navImageClasses = classNames(classes.imgRounded, classes.imgGallery);
return (
<div>
-- intentionally deleted copy --
</div>
);
}
}
export default withStyles(profilePageStyle)(About);
Then, src/routes/components/AboutPage/index.js:
import AboutPage from './AboutPage'
export default AboutPage
Then src/routes/About/index.js
import AboutPage from './components/AboutPage/index.js'
// Sync route definition
export default {
component: AboutPage
}
Then routes/index.js - shown as above.
I have read this several times- I can't make sense of it. If the child doesn't have state, then I don't see why a store would be relevant.
https://github.com/ReactTraining/react-router/blob/v3/docs/API.md#plainroute
Okay, I've spent three hours driving myself crazy trying to figure this out. I'm sure I'm doing something wrong, but this is my first foray into React and I'm not sure exactly what the problem is.
My main app file looks like this:
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory'
import routes from './routes.jsx';
import map from 'lodash/map';
import MainLayout from './components/layouts/main-layout.jsx';
const history = createHistory();
const App = ({store}) => (
<Provider store={store}>
<Router history={history} basename="/admin-panel">
<Route component={MainLayout}>
<Switch>
{map(routes, (route, name) => (
<Route key={name} path={route.path} exact={route.exact} component={route.component} />
))}
</Switch>
</Route>
</Router>
</Provider>
);
App.propTypes = {
store: PropTypes.object.isRequired,
};
export default App;
The route is simply a JSON object that contains route info. Example:
import React from 'react';
import DashboardContainer from './components/containers/dashboard-container.jsx';
import AdminUserContainer from './components/containers/admin-users-container.jsx';
export default {
dashboard: {
order: 1,
path: '/',
exact: true,
name: 'Dashboard',
icon: 'tachometer',
component: DashboardContainer,
},
users: {
order: 2,
path: '/admin-users',
name: 'Admin Users',
icon: 'users',
component: AdminUserContainer,
}
};
(There's irrelevant stuff in the object; that's for the sidebar rendering, which also uses this object to render itself)
dashboard-component.jsx looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Dashboard from '../views/dashboard.jsx';
class DashboardContainer extends Component {
render () {
return (<Dashboard />);
}
}
const mapStateToProps = function (store) {
return store;
};
export default connect(mapStateToProps)(DashboardContainer);
And dashboard.jsx looks like this:
import React, { Component } from 'react';
class Dashboard extends Component {
render () {
return (
<p>This is a test.</p>
);
}
}
export default Dashboard;
But for some reason, no matter what, the returned component is UnknownComponent. Apparently, nothing is matching on /admin-panel, and I'm not sure why. I've tried moving components around, merging the Routing stuff with MainLayout (which just contains the stuff that won't change every request, like the sidebar and the header), even tried the HashRouter instead of the BrowserRouter to see if that would do anything. No dice.
Can someone familiar with React tell me just what it is I'm not doing right here?
So it's my first time setting something like this up and I'm struggling a little bit.
I used https://github.com/reactjs/react-router/tree/master/examples/huge-apps as my source of learning and am trying to set up a very basic layout for a react application.
What seems to be happening is that react isn't re-rendering after a path change therefore nothing ever gets added to the dom
When I click on the go to home component Link the URL bar changes but no DOM changes occur...
Here is my code [i'm leaving out my directory structure since i don't think it's important for the problem]
index.jsx: Load up the react app and get all routes
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import Routes from './app/Routes.js';
render(
<Router
history={browserHistory}
routes={Routes}
/>,
document.querySelector('.js-mount-point')
);
Routes.js: Constant to keep all of my routes so that I don't have to manually specify them in index.js
import App from './App.jsx';
import Home from '../routes/Home/Home.js';
const Routes = {
path: '/',
component: App,
childRoutes: [
Home,
],
};
export default Routes;
App.jsx: Parent most component for my app
import React from 'react';
import { Link } from 'react-router';
const App = props => {
console.log(props);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="#/home">Go to Home Component</Link></p>
{props.children}
</div>
);
};
export default App;
Home.js Grab all my route information and getComponent lives here
const HomeRoute = {
path: 'home',
title: 'Home',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./HomeComponent.jsx').default);
});
},
};
export default HomeRoute;
HomeComponent.jsx my very basic home component
import React from 'react';
const HomeComponent = () => (
<div>
<h2>Welcome Home</h2>
</div>
);
export default HomeComponent;
Edit1: Made App.jsx pure function
Edit2: Fixed Routes.js
Edit3: Fixed Home.js
Edit4: Final fix,
const HomeComponent =
changed to
const HomeComponent = () => (
You App should be a component not a function returning an object. Right now you are mixing two approaches.
Either a function (stateless component)
import React from 'react';
import { Link } from 'react-router';
const App = props => {
console.log(props.children);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="/home">Go to Home Component</Link></p>
{props.children}
</div>
);
};
export default App;
Or a statefull component that has render method
import React, { Component } from 'react';
import { Link } from 'react-router';
class App extends Component {
render() {
console.log(this.props.children);
return (
<div>
<h1>Hello World!</h1>
<p><Link to="/home">Go to Home Component</Link></p>
{this.props.children}
</div>
);
}
};
export default App;
Same with HomeComponent
const HomeComponent = () => (
<div>
<h2>Welcome Home</h2>
</div>
);
And you need to fix route config as well
const HomeRoute = {
path: 'home', //no # needed
title: 'Home',
getComponent(nextState, cb) {
require.ensure([], (require) => {
cb(null, require('./HomeComponent.jsx').default);
});
},
};
Ohh, and I think you need.
const Routes = {
path: '/',
component: App,
childRoutes: [
Home
],
};
Everything is about isomorphic application. I'm using React with react-router module on server side for routing purposes and have following warning in browser console.
Warning: render(...): Replacing React-rendered children with a new
root component. If you intended to update the children of this node,
you should instead have the existing children update their state and
render the new components instead of calling ReactDOM.render.
I have following routes schema defined on backend:
<Route path="/" component={App} >
<IndexRoute component={Home} />
</Route>
App component:
module.exports = React.createClass({
render : function() {
return <html>
<head></head>
<body>
<div id="container">
{ this.props.children }
</div>
<script src="/app/bundle.js"></script>
</body>
</html>
}
});
Home component:
module.exports = React.createClass({
render : function() {
return <div>Any content here</div>
}
});
After that I use on the frontend:
ReactDOM.render(<Home />, document.getElementById('container'));
Probable solution:
If I understood correctly if I could render App component as static markup(renderToStaticMarkup) and Home component as a string (renderToString), then it would be ok.
Is it possible to implement something like that with react-router?
Assuming RR 1.03, your routing configuration looks fine.
Your app component should be like this:
module.exports = React.createClass({
render : function() {
return <html>
<head></head>
<body>
<div id="container">
{React.cloneElement(this.props.children,
{
anyOtherPropsHere: 'blablah'
}
)}
</div>
<script src="/app/bundle.js"></script>
</body>
</html>
}
});
Your server response -> render should look something like this. (taken from the documentation)
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './routes'
serve((req, res) => {
// Note that req.url here should be the full URL path from
// the original request, including the query string.
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
// You can also check renderProps.components or renderProps.routes for
// your "not found" component or route respectively, and send a 404 as
// below, if you're using a catch-all route.
res.status(200).send(renderToString(<RouterContext {...renderProps} />))
} else {
res.status(404).send('Not found')
}
})
})
And finally, somewhere on clientside load, do something like this. I've added the history library in this example to help.
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, match, RoutingContext } from 'react-router';
import history from 'utils/history';
import AppRoutes from '/app/AppRoutes';
// use this function to return a Route component with the right props
function createFluxComponent(Component, props) {
props = Object.assign(props, {
flux: window.flux.or.whatevs
});
return <Component {...props} />;
}
// add a global history listener perhaps?
history.listen(function(location) {
console.log('Transition to--------------', location.pathname);
});
// add window.router polyfill for transitionTo
window.router = {
transitionTo: function(t) {
return history.pushState(null, t);
}
};
// render your routing configuration, history and flux as props
ReactDOM.render(<Router createElement={createFluxComponent} history={history} routes={AppRoutes}/>, document);
What is most important is that you render to string using the RouterContext and render props on the server side. You get the renderProps from the RR match function, which will run your routes against the config and produce the right component in renderProps. Then you'll simply render the client side with Router element and the router config to the document. Does this make sense? It should work without any invariants.