I have a controller called PersonCreate. In this component, I have to check the role of the authenticated user (this.props.app.session.user.role.isAdmin()) and depending on the value, render some form.
The problem is, it takes a while to generate the session object. When I browse the endpoint for http://.../person/create, it tries to call this.props.app.session.user.role.isAdmin(), which throws null pointer exception because session is not generated yet.
My Router file looks like this.
class RootComponent extends React.Component<any, any> {
private generateSession() {
store.dispatch(SessionActions.generate());
}
public render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Layout}>
<Route path="app" onEnter={this.generateSession}>
<Route path="person">
<Route path="create" component={PersonCreate} />
<Route path="update/:id" component={PersonUpdate} />
<Route path="delete/:id" component={PersonDelete} />
...
Basically store.dispatch(SessionActions.generate()) generated a Saga which does a series of asyncronous stuff. (e.g validate token, obtain session information, refresh local storage etc.) I have to start rendering components after they complete.
Any ideas?
You can use check in RootComponent like this:
public render() {
if (!this.props.app.session) {
return null;
}
and it will render routes only after session will be initialized.
Or you can use similar check in PersonCreate component.
You can create a React Component that contains login for rendering. This component wraps all of the routes that require users information.
<Route path=”/” component={App}>
<Route path=”cart” component={Cart}/>
<Route path=”login” component={Login}/>
<Route component={EnsureLoggedInContainer}>
<Route path=”checkout” component={Checkout}/>
<Route path=”account” component={Account}/>
</Route>
</Route>
And in this "parent"(EnsureLoggedInContainer) component in render do something like:
render() {
if (isLoggedIn) {
return <AppropriateUserForm />;
} else {
return <LoadingIndicator />;
}
}
You can read more about this approach here:
https://medium.com/the-many/adding-login-and-authentication-sections-to-your-react-or-react-native-app-7767fd251bd1
Related
I try to put 2 HOC in switch but, only routers in first be called, the second is not called.
// if user is not login, show login page, otherwise add a side bar to children and show up
#inject("userStore")
#observer
class Auth extends React.Component {
render() {
let { userStore, children } = this.props;
return userStore.isLogin ? <CoreLayout>{children}</CoreLayout> : <Login />;
}
}
// if user is not login, show login page, otherwise show children
#inject("userStore")
#observer
class AuthWithoutLayout extends React.Component {
render() {
let { userStore, children } = this.props;
return userStore.isLogin ? children : <Login />;
}
}
export { Auth, AuthWithoutLayout };
And the Switch part:
<ConfigProvider locale={locale}>
<Switch>
<Route exact path="/" component={Login} />
<AuthWithoutLayout>
<Route path="/switch-role" component={SwitchRole} />
</AuthWithoutLayout>
<Auth>
<Route path="/user-list" component={UserList} />
</Auth>
</Switch>
</ConfigProvider>
If I input /localhost:3000/switch-role to browser, child page can show up correctly, but if I input /localhost:3000/user-list, I see a black page. if I remove AuthWithoutLayout part, the user-list page will show up.
Please help.
Switch
Renders the first child <Route> or <Redirect> that matches the location.
BTW, neither of those are Higher Order Components, but rather they are simple wrapper components. You can correct your Auth component, but your AuthWithoutLayout is a layout container and better suited to decorate anything other than a route or redirect.
Basically in your "auth" component you want to check some authentication condition and if authenticated render the Route, otherwise you redirect the user where you want them, usually the login path.
Your containers should also apply the Single Responsibility Principle, meaning an auth container should only concern itself with authentication, and a layout container should only concern itself with content layout.
Here's a sample auth route rewrite
// if user is logged in, render Route, otherwise Redirect to login "/"
#inject("userStore")
#observer
class AuthRoute extends Component {
render() {
const { userStore, ...props } = this.props;
return userStore.isLogin ? <Route {...props} : <Redirect to="/" />;
}
}
Usage:
<ConfigProvider locale={locale}>
<Switch>
<Route exact path="/" component={Login} />
<AuthRoute path="/switch-role" component={SwitchRole} />
<AuthRoute path="/user-list" component={UserList} /> // <-- use a layout container to decorate UserList!
</Switch>
</ConfigProvider>
The problem with above code is that Switch renders the First match component. So when you render AuthWithoutLayout without a Route, it assumes that this is the component that needs to be rendered and will not check any further and hence Auth is ignored
The solution is to write AuthWithoutLayout and Auth both with Routes
<ConfigProvider locale={locale}>
<Switch>
<Route exact path="/" component={Login} />
<Route path="/switch-role">
<AuthWithoutLayout>
<SwitchRole />
</AuthWithoutLayout>
</Route>
<Route path="/user-list">
<Auth>
<UserList />
</Auth>
</Route>
</Switch>
</ConfigProvider>
In React, I have code like this:
<Router basename='/app'>
<main>
<Menu active={menu} close={this.closeMenu} />
<Overlay active={menu} onClick={this.closeMenu} />
<AppBar handleMenuIcon={this.handleMenuIcon} title='Test' />
<Route path='/customers/:id' component={Customers} />
<Route path='/products/:id' component={Products} />
</main>
</Router>
Every time /customers/:id or /products/:id is accessed, I want to log the access by making an ajax call to a backend service. The fields I will be logging include the referrer URL, the current URL, and a randomly generated session ID (random base 64 integer) What's the best way to do that when I am using the Router component from react-router version 1.0.0.
This has been answered with this question https://stackoverflow.com/a/44410281/5746996
But in essense:
#withRouter
class App extends React.Component {
static propTypes = {
location: React.PropTypes.object.isRequired
}
// ...
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}
// ...
render(){
return <Switch>
<Route path="/" exact component={HomePage} />
<Route path="/checkout" component={CheckoutPage} />
<Route path="/success" component={SuccessPage} />
// ...
<Route component={NotFound} />
</Switch>
}
}
Use the location property in the props property when you have added the react-router. Test to see if it has changed with each update - if it has, you can send it.
Here is my Switch:
<Switch>
<Main>
<Route exact path="/login" component={LoginForm} />
<Route exact path="/register" component={RegisterForm} />
</Main>
</Switch>
The Main Component - is a Wrapper of the Children:
class Main extends Component {
render() {
const { children } = this.props;
return (
...
{children}
...
)
}
}
This return me all my (children) components as LoginForm as RegisterForm
So I need to get only one component depends of the route (props.location.pathname)
What is the right solution in that case?
Seems you need to put 'exact' for the first route tag also. It will solve the problem. As the '/' will get match to all the routes. So to make it specific, you need to put exact for first route as well.
I'm trying to do layouts with react-router.
When my user hits / I want to render some layout. When my user hits /login, or /sign_up I want the layout to render, with the relevant component for /login or /sign_up rendered.
Currently, my App.js looks like this
return (
<div className={className}>
<Route path="/" component={Auth} />
<ModalContainer />
</div>
);
My Auth.js looks like this
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
</AuthFrame>
);
So AuthFrame will get rendered when I hit /, and then react router looks for login or sign_up to render the other containers.
However, when I hit /, only the AuthFrame will render.
I would like for / to be treated as /login.
How do I achieve this?
The Switch component is useful in these cases:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Switch>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
{/* Default route in case none within `Switch` were matched so far */}
<Route component={LoginContainer} />
</Switch>
</AuthFrame>
);
see: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md
I think you're forced to introduce a prop/state which indicates the status of your viewer. This means is he signed in or just a guest of your website.
Your router can't obviously render /login if you you hit / but the router allows you to redirect to another page:
class AuthContainer extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
return <div>
<Route path="/login" component={LoginContainer}/>
<Route path="/sign_up" component={SignUpContainer}/>
</div>
}
}
class PublicHomePage extends React.Component {
render() {
return <div>
<Route path="/settings" component={SettingsComponent}/>
<Route path="/profile" component={ProfileComponent}/>
<Route path="/and_so_on" component={AndSoOnComponent}/>
</div>
}
}
class App
extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
const {loggedIn} = this.props;
if (loggedIn) {
return <PublicHomePage/>
}
return <Route exact path="/" render={() => (
<Redirect to="/login"/>
)}/>
}
}
I hope this code works for you. It isn't quite perfect but it should give you an idea how you could solve your problem.
In your case I would probably manipulate a bit with Routes in react-router. This code in AuthFrame should do the trick:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
{["/", "/login"].map((path, ind) =>
<Route exact key={ind} path={path} component={LoginContainer} />
)}
<Route exact path="/sign_up" component={SignUpContainer} />
</AuthFrame>);
Note the usage of exact on the routes, this is to prevent matching login component on /sign_up since it will also match / and prevent rendering both login and signup when accessing the root path (/).
I have a problem with react-router library. Please, look at my simplified routing:
<Router>
<Route path="/" component={Layout}>
<Route path="assets" component={Assets}>
<Route path=":id/definition" component={AssetDefinition}>
{ /* etc. ... */ }
</Route>
<Route path=":id/version" component={AssetVersion}>
<Route path=":verId/edit" component={EditVersion}></Route>
{ /* etc. ... */ }
</Route>
</Route>
<Route path="applications" component={Applications}>
<Route path=":id/definition" component={AppDefinition}>
{ /* etc. ... */ }
</Route>
<Route path=":id/version" component={AppVersion}>
{ /* etc. ... */ }
</Route>
</Route>
</Route>
</Router>
In Layout component I render Menu next to this.props.children rendered basing on routing. My Menu is being generated dynamically using this.props.route.path.
export default class Layout extends React.Component {
render() {
return (
<div>
<Menu />
{ this.props.children }
</div>
);
}
}
The problem is I don't know the appropriate routing path in Menu.
e.g.
I open /assets/123/version. Now I want to check AssetVersion component's route children (from this.props.route.childRoutes). I expect children elements inside this route (basing on given routing). But what I get is assets and applications, because Menu is higher and "fixed" in components tree.
How to access the most recent component's routing open in my app inside the Menu?