I'm trying to make a multilingual SPA with React and to rely on React-Router.
Here is a simplified version of the entry file:
// define the routes for each language ..
const InnerRoutes = (
<Route>
<IndexRoute page="home" component={InnerPage}></IndexRoute>
<Route path="(:page)" component={InnerPage}></Route>
</Route>
);
// define the routes for all the languages, using InnerRoutes ..
const AllRoutes = (
<Router history={browserHistory}>
<Route path='/' component={App} language="bg">
{InnerRoutes}
<Route path="en" language="en">
{InnerRoutes}
</Route>
</Route>
</Router>
);
// and render our app ..
ReactDOM.render(
AllRoutes,
document.getElementById('app')
);
So, I have the App top-level component and the InnerPage component.
The issue is:
When navigation change happens - InnerPage updates and (eventually) re-renders.
Inside InnerPage:
I have access to the requested page via the page property.
Unfortunately - I don't have access to the language property.
And that's why InnerPage can't pull the data from an end point - it knows the page, but not the language.
The router passes a routes prop to all route components, which is the set of matched routes. For example, on /, it would look something like this: [{ path: '/', language: 'bg'}, { }, { page: 'home' }].
So, depending on how you want to deal with the cases of having none or multiple languages declared by different matched routes, you can do something like this:
const lang = this.props.routes.reduce((lang, route) => {
return route.language || lang;
}, defaultLang);
which would prioritize the language given by inner routes.
Looking at your route config, there are two more things I should mention:
In your InnerRoutes, the page values for <IndexRoute page="home" ... /> and <Route path="(:page)" ... /> are exposed differently. The former is a prop on a route (like language) and can be accessed via this.props.route.page, while the latter is a URL param (this.props.params.page).
The English <IndexRoute> is not reachable, because the Bulgarian :page route comes before and matches as well. You can fix this by moving the English routes up.
Here's a jsbin demonstrating the points I mentioned: http://jsbin.com/siqake/5/edit?js,output
Related
In my render function I have
<Route path="/classes/:course" render={(match) => (
<Redirect to={`/classes/${match.params.course}/home`} />
)}>
<Route path="/home" component={Home} />
</Route>
For example if the param for "course" was "BIO1001", I want to redirect the page to "/classes/BIO1001/home" when I go to the page "/classes/BIO1001/". Previously I tried simply putting a Redirect tag with "from" and "to" but ran into the problem of the url actually going to "/classes/:course/home" instead of "/classes/BIO1001/home"
Also would the nested route with path="/home" go to "/classes/BIO1001/home"? I was unsure on how I can set a route where the path starts from the previous url (in this case starting from "/classes/:course/"
The first problem is right here:
render={(match) => ( ...
The render function gets a props object which contains a match property. Instead of destructuring the match property, what you are actually doing is assigning the whole props object to a variable match. So when you go to access match.params it won't be found.
You need curly braces around match in order to destructure it.
render={({match}) => ( ...
The second problem is the nesting of the two Route components. I get a warning:
Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored
So based on that warning you can see that your Redirect is being entirely ignored since it comes from render. The child Route is seen as the render function for the classes Route.
I'm assuming you have various subpages of a course? And we want to force the URL to include "/home" if none is set? (Personally I would do the opposite and redirect "/home" to the course root URL).
Previously I tried simply putting a Redirect tag with "from" and "to" but ran into the problem of the url actually going to "/classes/:course/home" instead of "/classes/BIO1001/home"
Per the docs, you can use params in your Redirect, but only if it is inside a Switch.
Here's a sample code to do that:
const CoursePage = () => {
// you can access arguments from the props or through hooks
const { course, tab } = useParams();
// not sure how you want to handle the different tabs
return <div>Viewing Course {course}</div>;
};
const App = () => (
<BrowserRouter>
<Switch>
<Route path="/classes/:course/:tab"><CoursePage/></Route>
<Redirect from="/classes/:course" to="/classes/:course/home"/>
</Switch>
</BrowserRouter>
);
export default App;
Your nested routing is true i think. But you are rendering your Home component without any dynamic props. Try it like below:
<Route path="/classes/:course" render={(match) => (
<Redirect to={`/classes/${match.params.course}/home`} />
)}>
<Route path="/home" >
<Home someProps={someValue} />
</Route>
</Route>
Note: The Redirect element can be used without a containing Route element by providing a from property. In this case, you can just use the URL parameter tokens in both from and to, and they'll be carried over for you. For example, if you're using a Switch block...
<Switch>
{/* various app routes... */}
{/* redirect action */}
<Redirect from="/classes/:course" to="/classes/:course/home" />
<Switch>
I'm reading about static vs dynamic routing in React Router, and I'm struggling to identify the advantages of the latter (and why v4 chose to go with it). I can see the advantage of listing out all the routes for an application (static), as well as the component that each route maps to, allowing you to trace what would be rendered given a specific URL. But I'm not seeing any clear advantage to dynamic routes.
If anything, I can only see disadvantages, because there is no clear way to see what state a URL will map to, without starting at the root app element and working your way through the routes (though I might be mistaken).
What situations does dynamic routing address? Why is it preferable to static routing (maybe specifically in React apps)?
Dynamic Routing
From the react router docs:
When we say dynamic routing, we mean routing that takes place as your
app is rendering, not in a configuration or convention outside of a
running app.
Think of routes as components
The earlier versions of react-router (pre v4) used to have static routes. This led
to a centralized routing in apps like:
<Router>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='about' component={About} />
<Route onEnter={verifyUser} path='profile' component={Profile} />
...
</Route>
</Router>
However, this is not exactly the React way of doing things. React focuses on composition using components based logic. So, instead of imagining our Routes as a static system, we can imagine them as components, which is what react-router v4 brings in and the primary philosophy behind it.
Therefore, we can use Route as we would use any React component. This lets us add Route components as and when we build different components. One advantage of doing this is we can decouple the routing logic to the components needing them.
Nesting routes
The About component can handle all the routes and conditionally render parts of UI based on the url (say /about/job or /about/life etc).
Another thing to note is that a Route component will either render the component for a matching route or null. Example, the following Route renders the About component for a route /about and null (or nothing) otherwise.
<Route path='about' component={About} />
This is also similar to how we're used to conditionally rendering components in React:
route === '/about' ? <About /> : null
Now if we need to render some other components inside the About component for routes /about/job or /about/life we can do it like:
const About = ({ match ) => (
<div>
...
<Route path={`${match.url}/job`} component={Job} />
<Route path={`${match.url}/life`} component={Life} />
</div>
)
Dynamic imports and code splitting
Personally, I've also found this approach works better for me in case I'm using dynamic imports with code-splitting, since I can add dynamic routes in any of my components. For example,
import Loadable from 'react-loadable';
const Loading = () => (
<div />
);
const Job = Loadable({
loader: () => import('./Job'),
loading: Loading,
});
const Life = Loadable({
loader: () => import('./Life'),
loading: Loading,
});
...
render() {
return (
...
<Route path={`${match.url}/job`} component={Job} />
<Route path={`${match.url}/life`} component={Life} />
)
}
Responsive routes
Another great use case for dynamic routing is creating responsive routes which is explained beautifully in the react router docs and a recommended read. Here's the example from the docs:
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices}/>
</AppLayout>
)
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Summarizing the docs, you'll notice how simple and declarative it becomes to add the Redirect to large screen sizes using dynamic routing. Using static routing in such cases would be quite cumbersome and would need us to put all the routes in a single place. Having dynamic routing simplifies this problem since now the logic becomes composable (like components).
Static Routing
There are some problems which are not solved easily with dynamic routing. An advantage of static routing is that it allows for inspection and matching of routes before rendering. Hence it proves useful especially on server side. The react router team is also working on a solution called react-router-config, quoting from which:
With the introduction of React Router v4, there is no longer a
centralized route configuration. There are some use-cases where it is
valuable to know about all the app's potential routes such as:
Loading data on the server or in the lifecycle before rendering the next screen
Linking to routes by name
Static analysis
Hope this provides a good summary of both Dynamic Routing and Static Routing and the use cases for them :)
According to the React-Router docs:
When we say dynamic routing, we mean routing that takes place as your
app is rendering, not in a configuration or convention outside of a
running app. That means almost everything is a component in React
Router.
Its clear for the explanation that, all you Routes are not initialised at the start of your application,
In React-router v3 or below, it used static Routes and all Routes would be initialised at the top level, and nesting used to be achieved like
<Router>
<Route path='/' component={App}>
<IndexRoute component={Dashboard} />
<Route path='users' component={Users}>
<IndexRoute component={Home}/>
<Route path="users/:id" component={User}/>
</Route>
</Route>
</Router>
With this API setup, react-router was reimplementing parts of React (lifecycles, and more), and it just didn’t match the composition logic that React recommends on using.
With Dynamic Routes the following advatages, comes to be foreseen
Nested Routes
Nested Routes with Dynamic Routing are more like
const App = () => (
<BrowserRouter>
{/* here's a div */}
<div>
{/* here's a Route */}
<Route path="/todos" component={Todos}/>
</div>
</BrowserRouter>
)
// when the url matches `/todos` this component renders
const Todos = ({ match }) => (
// here's a nested div
<div>
{/* here's a nested Route,
match.url helps us make a relative path */}
<Route
path={`${match.path}/:id`}
component={Todo}
/>
</div>
)
In the above example, only when /todos matches the route-path, the Todo component is mounted and only then the Route path /todos/:id is defined.
Responsive routes
The React-router docs have a good use case for this.
Consider a user navigates to /invoices. Your app is adaptive to different screen sizes, they have a narrow viewport, and so you only show them the list of invoices and a link to the invoice dashboard. They can navigate deeper from there.
However on a large screen, navigation is on the left and the dashboard or specific invoices show up on the right.
and hence /invoices is not a valid Route for a large screen and we would want to redirect to /invoices/dashboard. This may so happen, the user rotates his/her phone from a portait to a landscape mode. This can easily be done using dynamic Routing
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Using Dynamic Routes with React Router’s, think about components, not static routes.
Code Splitting
One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it. You can think of code splitting as incrementally downloading the app. This is made possible with Dynamic Routing.
The advantages it brings is that all your code need not be downloaded at once and hence it makes initial rendering faster.
Here is a good article that helps you setUp codeSplitting for your application
Writing Composable Authenticated Routes
With Dynamic Routing its also made easier to write PrivateRoutes(an HOC that does authentication) which allow for authenticating users and providing them access to specific Routes and redirecting otherwise. This call all me made very generically
A Typical Private Route would be look like
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
and can be used as
<PrivateRoute path="/protected" component={Protected} />
When setting a react-router app, I often set it on callbacks to represent authorization filters. this leads me to such designs:
# before, in my root component
requireAuth() { ... }
noAuth() { ... }
render() {
return (
<Router history={this.props.history}>
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Router>
)
}
I am now trying to port this to nodejs and render the page in the server. Suddenly, I'm asked to group all the routes in a const. Second, I lose my root component and those this. callback binds, and am not able to recreate the same architecture on the server.
So my first problem is the route. In react, I can't return groups of components, but a component which encapsulates all the rest. So, this was my attempt:
# root component render
# after import routes from './routes'
render() {
return (
<Router routes={routes} history={this.props.history}/>
);
}
# in routes.js
module.exports = (
<Route> # is this right?
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Route>
So, this doesn't seem to cut it and I get a few errors. First, I'm encapsulating all routes inside a main Route component. Is this correct, from a react-router perspective? Can I have kind of empty-encapsulating components?
Second would be: How do I bind those this. callbacks on the server side now? Since I'm following the tutorials, My express router looks smth like this:
import routes from './src/components/routes';
...
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
....
But it's breaking with:
onEnter: undefined.requireAuth,
^
TypeError: Cannot read property 'requireAuth' of undefined
Which is right, as routes constant is not bound to any context.
Or is there a more proper way to do this in react-router, and binding callbacks to routes is the wrong approach?
I am currently using React Router and have routes that use the browserHistory, for example:
import { Router, Route, IndexRedirect, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="tasks" component={Tasks}>
<IndexRoute component={List} />
<Route path="/:id" component={Item} />
</Route>
</Router>
Which allows me to do routes such as:
/tasks/
/tasks/1234
This works, but we have come across a situation where we have two views that are displayed at the same time. We'd like for the link to be shareable and have the app open with both views.
So for example, if we have tasks on the left side of the screen, and a shop on the right, we'd like for there to be two independent parts of the path, something like:
/tasks/1234/#/shop/item/xyz
The shop route should be independent of the left of the hash, and the tasks route should be independent of the right of the hash, so that /new-feature/xyz/#/shop/item/xyz should still render the same view on the right side of the window.
Is it possible to have React Router do routes like this? Or will I have to write a custom router to solve this?
I'm guessing I'd basically have to combine the browserHistory and hashHistory together, but I don't think that's possible with React Router out of the box.
I think rolling your own router just to handle this case might be a little overboard. You can have as many different paths in your route config as you want, and access param information in your components:
<Router history={browserHistory}>
<Route path="/" component={App} />
<Route path="/tasks/:taskId" component={App} />
<Route path="/shop/:shopId" component={App} />
<Route path="/tasks/:taskId/shop/:shopId" component={App} />
</Router>
let App = ({ params }) => // simplified
<div>
{ params.shopId && <Shop /> }
{ params.taskId && <List /> }
</div>
Just a thought.. I think there are several ways to augment this to handle more complex scenarios but this would definitely work for what you've specified in the question (for the most part).
Update: Above is 'hardcoded', but of course you do not need to write out every combination by hand. This is what loops are for.
import * as routeComponents from './routeComponents'
<Router history={browserHistory}>
{ routeComponents.map(r => <Route {...r} />) }
</Router>
let App = ({ params }) =>
<div>
{ routeComponents.reduce((matches, r) => ([
...components,
...(params[r.slug] ? [r.component] : [])
]), [])}
</div>
We have developed our own router Boring Router with "parallel routing" support, though it depends on MobX and works differently with React Router in many ways.
In Boring Router, a parallel route is stored as query string parameter prefixed with _. E.g., /app/workbench?_sidebar=/achievements.
Check out a complete example here.
I have a particular situation where I would like to load a different route rather than the initial request route while keeping the app universal/isomorphic.
So I have some routes like so
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="potato" component={Potato}/>
<Route path="apple" component={Apple}/>
</Route>
</Router>
On the initial request of / the server decides based on some other information elsewhere what other route should be loaded instead, ie the apple route should be rendered instead but the url on the front end should remain /
At first this is quite trivial as you can just change the url provided to match
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
However, the client first displays correctly then a few seconds after it loads the original request route rather than what the server decided.
It would display an error like this
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
This makes sense as I told the server to render the /apple when the / route was hit. But when it gets to the client side it goes 'Um the route is / but the markup is showing something different to what should be there.
So is there anyway I can fool the client that everything is well and correct?
One solution is just do a 301 redirect but this means the client side
will end up showing /apple rather than just / in the url
I'd like to avoid a 301 for SEO and to keep the initial url route that the user typed
Any solution for this bonkers request?
I'm using
react:15.0.2
react-router: 2.4.0
react-redux:4.1.2
redux: 3.1.2
You can define the routes on the server before calling match and let the client pick them up on the client.
Something like this on the server:
function getRoutes(something) {
if (something === 'apple') {
return (
<Route path="/" component={Apple}>
<Route path="potato" component={Potato}/>
</Route>
);
}
return (
<Route path="/" component={App}>
<Route path="potato" component={Potato}/>
<Route path="apple" component={Apple}/>
</Route>
);
}
const routes = getRoutes('apple');
<script dangerouslySetInnerHTML={{__html: `window.__INITIAL_ROUTES__ = ${routes}`}}></script>
<Router history={createMemoryHistory()}>
{routes}
</Router>
...and on the client:
const routes = window.__INITIAL_ROUTES__;
<Router history={browserHistory}>
{routes}
</Router>