how to have seperate admin store that is not loaded by a non admin user? - mobx

I want to create a mobx store that is only loaded by admin users. It will only be injected into anything along the /admin route. I've looked into the use of Nested Providers but without much luck.
Ideally I'd like it to be linked to the route, so if a user goes to domain.com/admin then it is loaded.
I could write botchy code that simply checks if the user is admin, but I'm hoping theres a more elegant pattern I could follow.
EDIT
heres my temporary solution, let me know if theres a better way
AdminRoutes
export const AdminRoutes = () => {
const adminStores = createAdminStores()
return (
<Switch>
<Provider {...adminStores}>
<AuthedRoute path="/admin" exact component={Admin} />
</Provider>
</Switch>
)
};
App.ts
export const App = hot(({ history }: any) => (
<Provider {...rootStores}/>
<Router history={history}>
<Switch>
<UnauthedRoute path="/login" exact component={Login}></UnauthedRoute>
<Route path="/admin" exact component={AdminRoutes} />
<Route path="/error" component={ErrorComponent} />
<Route path="/abc" component={Test} />
<Route path="/:path" component={Issue} />
<Route path="/" component={Home} />
</Switch>
</Router>
</Provider>
));
I create the admin stores only when the user runs the AdminRoutes route. The only issue with this is that the store reloads when the user changes the URL however that happens with all my other stores.

One thing you could improve is to use React.lazy with your whole Admin component.
// import it like that
const AdminRoutes = React.lazy(() => import('./path-to-admin-routes'))
...
// and use like that
<Route
path="/admin"
render={() => (
<Suspense fallback={'Loading'}>
<AdminRoutes />
</Suspense>
)}
/>
That way regular non-admin users won't even load admin stuff and your final bundle will be smaller.
More info: https://reactjs.org/docs/code-splitting.html#reactlazy
Or you could also inject your AdminStore inside RootStore dynamically. I made example for it, hope it makes sense https://codesandbox.io/s/httpsstackoverflowcomquestions62759240-ew8hf?file=/src/AdminComponent.js
Basically you create your AdminStore first time admin component is loaded and then use newly created instance by saving it inside RootStore

Related

React-Admin: How to redirect to <Dashboard> when authenticated through custom <Login> page

react-admin loads the <Dashboard> at the base "/". And for that reason, the custom page never opens by default because the <Admin> always routes to the dashboard.
I've implemented <PrivateRoutes> to handle authentication and it's a success.The <Login> page is loaded by default, and upon authentication, it routes to dashboard.
Challenge: This process breaks the functionality of Sidebar <links>, like "Users".
// CustomRoutes.js
export default [
<Route exact path="/login" component={LoginPage} noLayout />,
<Route exact path="/forgot-password" component={ForgotPassword} noLayout />,
<Route exact path="/confirm-password" component={ConfirmForgotPassword} noLayout />,
<PrivateRoute path="/" loggedIn={localStorage.getItem("access_token")} component={dashboard} />
];
And then...
// PrivateRoute.js
import React from "react";
import { Redirect, Route } from "react-router-dom";
const PrivateRoute = ({ path, component: Component, ...rest }) => {
const isLoggedIn = localStorage.getItem("access_token");
return (
<Route
path={path}
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect to={{ pathname: "/login", state: { from: props.location } }} />
}
/>
);
};
export default PrivateRoute;
Here's how it looks within the main <App>
// App.js
const App = () => (
<Admin
theme={myTheme}
dashboard={dashboard}
authProvider={authProvider}
dataProvider={dataProvider}
customRoutes={customRoutes}
loginPage={LoginPage}
logoutButton={LogoutButton}
forgotPassword={ForgotPassword}
>
<Resource name="users" list={UserList} create={UserCreate} show={UserShow} icon={UserIcon} />
</Admin>
);
export default App;
Please note that the <resource> "users" is not working.
Do I need to add custom route for that too?
Alright, let's debug this step-by-step:
We can take advantage of react-admin directly to handle the authentication via the authProvider.
Once you include the authProvider, react-adminenables a new page on the /login
route, which displays a <Login> form asking for username and password.
You can definitely customize that login page (as I assume you did),
and I advise that you avoid using a <PrivateRoute> since the authProvider redirects all users who aren't yet authenticated to /login.
Kindly note that <Admin> creates an application with its own state, routing, and controller logic.In your code, you added forgotPassword which is not a prop accepted by <Admin.
<admin
// remove this...
- forgotPassword={ForgotPassword}
>
I wonder exactly what error you get in this case, but this is the main bug.

React to url change within component with React Router 4?

I have a component available at different urls. This is a simplified version of my app.js
<Router>
<Switch>
<Route path="/login"
render={() => {
return <SignUpAndRegister status="login" />;
}}
</Route>
<Route path="/register"
render={() => {
return <SignUpAndRegister status="register" />;
}}
</Route>
</Switch>
<Router>
I pass the status prop and then in the component setState based on the prop value. This works so far but I also need Links within the component that link to other other state. When your on the register page/state I have a link to login. When you're on the login page/state I have a link to register.
This means I have to have a link to change the url and also call a function to setState:
<Link
to="/login"
onClick={() => this.registerOrLogin('login')}
>
Log in
</Link>
How can I have the components state change when a url change is detected? It seems like browserHistory did this prior to v4 but I cant find an up up date solution.
I can set the state within the SignUpAndRegister component when it first mounts and then every time new props are received eg when the url changes.
componentDidMount() {
this.setState({ status: this.props.status });
}
componentWillReceiveProps(props) {
this.setState({ status: props.status });
}

I am not able to set a specefic path in test

Problem: My App component is responsible to render the other components based on the path. So inorder to test it, I need to be able to "set" the path. e.g I'd like to see the SignUp component is rendered when the path is /sign-up.
What I've done: I thought I could use initialEntries to give the MemoryRouter an initial path (unsuccessful) then I thought I might be able to use Route/Redirect directly in my test inside MemoryRouter to set the path, but that did not work either.
Specification:
React 15
React Router 4 (react-router-dom)
Redux 3.6
Jest (the one comes with create-react-app)
Yo can find the code on the branch with the issue
I spent a while trying to figure this out myself.
Simply put you cannot nest a <Router> within the <MemoryRouter> as they are both Routers.
Solution Example:
Routes.js - Remove <BrowserRouter> from this so you can test your code.
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import SignUp from '../SignUp/SignUp'; //your signup component
import Home from '../Home/Home'; //some other component for an example
const MyRoutes = () => (
<Switch>
<Route path="/" component={Home} />
<Route path="/signup" component={SignUp} />
</Switch>
);
export default MyRoutes;
index.js - Add <BrowserRouter> here as you will need to wrap a <Switch> in a <Router>.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Components/Routes/Routes';
ReactDOM.render(
(
<BrowserRouter>
<Routes />
</BrowserRouter>
), document.getElementById('root')
);
tests/Routes.test.js - Now add the <MemoryRouter> in here for testing.
import React from 'react';
import ReactDOM from 'react-dom';
import { MemoryRouter } from 'react-router-dom';
import Routes from '../Components/Routes/Routes';
const TestRoute = props => (
<MemoryRouter initialEntries={props.initialEntries}>
<Routes {...props} />
</MemoryRouter>
);
it('at /signup it displays the signup page', () => {
const div = document.createElement('div');
ReactDOM.render(<TestRoute initialEntries={['/signup']} />, div); // render on the route "/signup"
const elem = div.getElementsByClassName('signup')[0];
expect(elem).not.toBe(undefined); // check an element exists with that class
if(elem !== undefined) {
expect(elem.innerHTML).toEqual('signup'); // check it has worked
}
});
In other words you are separating the Routes/Switch from the Router to enable you to be able to test it, as you cannot nest two Routers.
I'm having a similar issue, I have found that the following works for testing components whose behaviour changes based upon the route:
<MemoryRouter initialEntries={['/about']}>
<Navigation {...props} />
</MemoryRouter>
In this instance, the snapshot shows that an .active className is added to the 'about' link in Navigation.
What does not seem to work is adding memory router around the component/container holding the initial routes. To keep things simple I have reduced my basic routing page to this:
<Router>
<div className="App">
<Navigation app={this.props.app} />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/login" render={() => <Login app={this.props.app} />} />
<Route exact path="/signup" render={() => <Signup app={this.props.app} />} />
<Route exact path="/account" render={() => <Account app={this.props.app} />} />
<Route component={ErrorPage} />
</Switch>
</div>
</Router>
The react-router team states in current testing docs:
We have a lot of tests that the routes work when the location changes,
so you probably don’t need to test this stuff.

React Router - Cannot GET

I have created simple web application running on a MERN stack, I have just tried to set up my routing with react-router. Now I know this question has been asked before but answers were suggesting adding options to Webpack whereas I am using Browserify / Gulp.
My problem is that my all my routes except for root are returning with CANNOT get.
var Router = require('react-router').Router;
var Route = require('react-router').Route;
var Redirect = require('react-router').Redirect;
var browserHistory = require('react-router').browserHistory;
class Routes extends React.Component {
render() {
return (
<Router history={browserHistory}>
<Route path="/bugs" component={BugList} />
<Redirect from="/" to="/bugs"/>
<Route path="*" component={NotFound} />
</Router>
);
}
}
Firstly, I believe you need to import the components you're routing to. So for example:
var BugList = require('./components/bugList');
Also, I think you can remove the slashes except for the root route. So this should work:
<Route path="bugs" component={BugList} />

Routes with react-router 1.0.0-rc1, express, web pack

I am fairly new to react and express, so sorry for the newb questions.
In react-router v013.3, I used the below config and "this.context.router.transitionTo(payload.route);" to switch routes, and it worked fine.
However, in react-router v1.0.0-rc1, I've tried both "this.props.history.pushState(null, payload.route);" as well as "replaceState", but they don't render my route.
I cannot use the class since I have to switch routes programmatically.
This suggests that I add some code to my express config (please see below): https://github.com/rackt/react-router/blob/master/docs/guides/basics/Histories.md#createbrowserhistory
I also added 'historyApiFallback: true' to my webpack dev server config.
Although via console.log message I see history is receiving the route change request, my requested routes do not render.
Can anyone see what I'm missing?
Thanks!!!
// react-router v013.3
const AppRoutes = (
<Route path="/" handler={App}>
<DefaultRoute handler={Home} />
<Route name="myFieldMapping" handler={MyFieldMapping} />
<Route name="myValidation" handler={MyValidation} />
</Route>
);
Router.run(AppRoutes, Router.HashLocation, (Root) => {
React.render(<Root />, document.getElementById('content'));
});
// react-router v1.0.0-rc1
const myHistory = useBasename(createHistory)({
basename: '/myApp'
});
let unlisten = myHistory.listen(function (location) {
console.log('---...--- CALL TO history: ', location.pathname)
});
React.render((
<Router history={myHistory}>
<Route path="/" component={App} >
<IndexRoute component={Home} />
<Route path="myFieldMapping" component={MyFieldMapping} />
<Route path="myValidation" component={MyValidation} />
</Route>
</Router>
), document.getElementById('content'));
// express config
app.get('/myApp/*', function(request, response){
response.sendFile(path.resolve(__dirname, publicPath, 'index.html'))
});