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'))
});
Related
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
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.
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.
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} />
I want my routes to look like:
/
/signin
/discussion/:title
However, anything past the second forward slash causes an error, and all of my client-side dependencies are throwing Unexpected token errors. For instance, reactjs, my CSS, nor my image files are being loaded in my index.html file. I'm using ExpressJS on the backend. I use the following line on my server to push the routing to the client:
app.get('*', function(req, res, next) {
res.sendFile(path.join(__dirname, 'app/index.html'));
});
routes.jsx
var React = require('react'),
Router = require('react-router'),
Route = Router.Route,
IndexRoute = Router.IndexRoute,
App = require('./components/app/app.jsx'),
Home = require('./components/pages/home.jsx'),
Discussion = require('./components/pages/discussion.jsx'),
DiscussionArea = require('./components/pages/discussionArea.jsx'),
Signin = require('./components/pages/signin.jsx'),
NotFound = require('./components/pages/notFound.jsx');
var routes = (
<Route path='/' component={App}>
<IndexRoute component={Home} />
<Route path='discussion' component={Discussion}>
<Route path='/discussion/area' component={DiscussionArea} />
</Route>
<Route path='signin' component={Signin} />
<Route path='*' component={NotFound} />
</Route>
);
module.exports = routes;
main.jsx
var React = require('react'),
ReactDOM = require('react-dom'),
ReactRouter = require('react-router'),
Router = ReactRouter.Router,
routes = require('./routes.jsx'),
createHistory = require('history').createHistory;
ReactDOM.render((
<Router history={ createHistory() }>
{routes}
</Router>
), document.getElementById('app'));
Why are my nested routes not mounting using react-router?
I found the answer. Refer to this Stackoverflow answer: Minimum nodejs server setup needed for React using react-router and browserHistory
It has something to do with the way my server is serving the paths. My resources are being loaded relative to the URL, when they should be loading from the root of the server.
The solution was to add <base href="/"> to the head of my index.html file.