I am writing routes with react router:
<Route path="/" element={<Homepage />} />
I have an array with the element names:
const page = ["Hompage", "About"];
How can I use the array element as a route element?
I tried to add strings of angle brackets and use the array element but it didn't work.
const edit = () => {
for (let i=0; i<10; i++) {
page[i]="<"+page[i]+" />"
}
Thanks
You will need to import the actual components you want to render at some point, and then map the array to JSX.
Example:
import HomePage from '../path/to/HomePage';
import About from '../path/to/About';
const pageMap = {
HomePage,
About
};
...
const pages = ["Hompage", "About"];
...
const edit = () => {
return pages.map(Page => <Page key={Page} />);
};
...
If you are wanting to map the pages to Route components, then it would be a similar process.
const pageMap = {
HomePage: { path: "/", element: <HomePage /> },
About: { path: "/about", element: <About /> },
};
const pages = ["Hompage", "About"];
...
pages.map(({ path, element }) => (
<Route key={path} path={path} element={element} />
))
At this point though, you may as well use the useRoutes hook and pass your routes config to it.
Example:
import { useRoutes } from 'react-router-dom';
import HomePage from '../path/to/HomePage';
import About from '../path/to/About';
...
const routesConfig = [
{ path: "/", element: <HomePage /> },
{ path: "/about", element: <About /> }
];
...
const routes = useRoutes(routesConfig);
...
return routes;
You should be able to do something like this:
import Home from '../Home'
import About from '../About'
const Routes = () => {
const pages = [{ route: '/', page: Home }, { route: '/about', page: About }]
return (
<Switch>
{pages.map(({route, page: Page }) => (
<Route path={route} element={<Page />} />
)}
</Switch>
)
}
The key here is to use the imported component as the value for your page key and then you can use normal JSX component syntax as you are iterating with map
Related
I am learning React. But I am familiar with Vue. In Vue with the Vue Router, we can have an array of objects based routing like,
const routes = [
{
name : "Login",
path : "/login",
component : ()=> import('views/Login.vue') //for lazy loading
meta : {
auth : false // this means no authentication needed for this particular route
}
},
{
name : "Dashboard",
path : "/dashboard",
component : ()=> import('views/Dashboard.vue') //for lazy loading
meta : {
auth : true // this means authentication needed for this particular route if the user is not authenticated, they will be redirected to login page
},
}]
What I tried so far is as below :
Login.jsx
const Login = () => {
const onClick = () => {
localStorage.setItem("token", "admin");
};
return <button onClick={onClick}>Login</button>;
};
export default Login;
Dashboard.jsx
const Dashboard = () => {
return <h1>Dashboard</h1>;
};
export default Dashboard;
App.js
import React, { Fragment } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import Dashboard from "./Dashboard";
import Login from "./Login";
const App = () => {
const routes = [
{
path: "/login",
component: Login,
auth: false,
},
{
path: "/dashboard",
component: Dashboard,
auth: true,
},
{
path: "/example",
component: Example,
auth: true,
},
];
return (
<>
<Switch>
{routes.map((route, index) => {
return (
<Fragment key={index}>
{route.auth ? (
<>
<Route
exact
path={`${route.path}`}
component={route.component}
/>
</>
) : (
<>
<Route
exact
path={`${route.path}`}
component={route.component}
/>
</>
)}
</Fragment>
);
})}
</Switch>
</>
);
};
export default App;
But in the above approach, I am always getting redirected to "/login". Is there anyways to fix this? Thanks in advance
Maybe this React-View-Router package help you. Instead of react-router-dom, try this package, and you can follow vue-router like syntax
First of all, you should not use Router in React like this.
video: https://www.youtube.com/watch?v=Law7wfdg_ls
Doc: https://reactrouter.com/
what is happening in your code is:
you are running a map and the first item is "login" that's why when the first item of your array came to the map it redirect to "login" and the moment this happens your application is switched to a different route and the next two iterations for your map will not execute.
this is how I do this in any application and it's really good for high scale applications:
(This is the sudo code)
// code for your router.js JSX
<BrowserRouter>
<Switch>
// you can define your other routes here
<AppRouter
path={"/profile"}
exact
layout={AuthLayout}
component={Profile}
/>
</Switch>
</BrowserRouter>;
// below functions ideally should written in a utility file
const AuthLayout = withRouter((props) => {
const [View, setView] = useState(null);
useEffect(() => {
setView(
// you can write your own check here
localStorage.getItem("access_token") ? (
props.children
) : (
<Redirect to="/" />
)
);
}, [props]);
return (
<div className="App-Main-Div">
<div className="main-section">
<Base header footer pathName={pathName}>
{View}
</Base>
</div>
</div>
);
});
const AppRouter = withRouter(
({ component: Component, layout: Layout, ...rest }) => (
<Route
{...rest}
render={(props) => (
<>
<Layout>
<Component {...props}></Component>
</Layout>
</>
)}
></Route>
)
);
// after doing all this you just need to put your router file in your App.js return statement
// which might look like this
const App =()=>{
// this router is your custom router you need to import it also
return <Router/>;
}
I am trying to achieve the following using a single router config array. The thing to note here is that I have a state variable, storeName, which I am passing to the CategoryPage component.
import React, { useState } from 'react'
import { Switch, Route, BrowserRouter } from 'react-router-dom'
import { Navigation } from './Navigation'
import { HomePage } from './HomePage'
import { CategoryPage } from './CategoryPage'
import { ProductPage } from './ProductPage'
import { PageNotFoundPage } from './404Page'
export function App() {
const [storeName, setStoreName] = useState('My Store')
return (
<div>
<h1>{storeName}</h1>
<BrowserRouter>
<Switch>
<Route path="/category">
<CategoryPage storeName={storeName} />
</Route>
<Route path="/product">
<ProductPage />
</Route>
<Route path="/" exact>
<HomePage />
</Route>
<Route path="*">
<PageNotFoundPage />
</Route>
</Switch>
</BrowserRouter>
</div>
)
}
If I was to switch to using a route config object like so...
const routeConfig = [
{
path: '/',
exact: true,
component: HomePage,
},
{
path: '/category',
exact: false,
component: CategoryPage,
},
// ...
]
// ...
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
<route.component />
</Route>
)
})}
</Switch>
...what would be the most appropriate way to pass down props keeping in mind each component might need different props?
I suppose I could try to make the component key of the route config items a function which accepts every prop and then tries to map it to the component being returned. I'm not sure if this is the right way though.
Thanks!
Update #1
So I tried instead to return the component from the route config array like so:
const routeConfig = [
{
path: '/',
exact: true,
component: (props) => <HomePage {...props} />,
},
{
path: '/category',
exact: false,
component: (props) => {
return <CategoryPage {...props} />
},
},
]
// ...
const [storeName, setStoreName] = useState('My Store')
// ...
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
{route.component({ storeName })}
</Route>
)
})}
</Switch>
This is working, but every component now would have every prop. Like in this case my HomePage component doesn't need the storeName prop but it still has it. Once I start adding other components which need other state variables, maybe this could cause a lot of variables to be stored in memory? It doesn't seem ideal.
Update #2
It's possible I'm going in the wrong direction by using route configs. Seems like that is actually opposite to react router's philosophy as I understand it from here https://reacttraining.com/react-router/web/guides/philosophy. Maybe I'll stick with my first implementation w/o the route config, but it'd still be nice to know how using the route config and passing in the correct props can be achievable.
In your Update #1 just extract the props you want
const routeConfig = [
{
path: '/',
exact: true,
component: () => <HomePage />,
},
{
path: '/category',
exact: false,
component: ({storeName}) => {
return <CategoryPage storeName={storeName} />
},
},
]
Or you could use a list of relevant props for each component and extract them on the go
function pickProps(availableProps = {}, selection = []) {
return selection.reduce(
(props, selectedProperty) => {
props[selectedProperty] = availableProps[selectedProperty];
return props;
},
{}
);
}
// ...
const routeConfig = [{
path: '/',
exact: true,
component: HomePage,
},
{
path: '/category',
exact: false,
component: CategoryPage,
props: ['storeName'],
},
// ...
]
// ...
const componentProps = {storeName}
//....
<Switch>
{routeConfig.map((route, i) => {
return (
<Route
path={route.path}
exact={route.exact}
key={i}
>
<route.component
{...pickProps(componentProps, route.props)}
/>
</Route>
)
})}
</Switch>
Have a couple of routes with params (like. /user/:user, car/:car/:year)
I'm trying to avoid to manually parse location.pathname if it's possible to use react-router (v3) for it.
How can I find the route that match to the current url location.
Need something similar to:
if (router.match_to_route('/user/:user')) {
... do something
}
...
The method matchRoutes in https://github.com/ReactTraining/react-router/blob/v3/modules/matchRoutes.js might be the one that I need.
Thanks.
Updated:
It can be achieved by
import { match } from 'react-router';
const routes = router.routes;
match({ routes, location }, (error, redirect, renderProps) => {
const listOfMatchingRoutes = renderProps.routes;
if (listOfMatchingRoutes.some(route => route.path === '/user/:user')) {
...
}
}
https://github.com/ReactTraining/react-router/issues/3312#issuecomment-299450079
I have done this using react-router-dom. I simplified the code so that you can easily understand it. I just passed the param user with my dashboard route in main App component and access it using this.props.match.params.user in my child component named as Dashboard.
App.js file
class App extends React.Component {
constructor(props) {
super(props);
this.state = {open: false};
this.state = {message: "StackOverflow"};
}
render(){
return (
<Router>
<div>
<Route exact path="/dashboard/:user" render={props => <Dashboard {...props} />} />
<Route exact path="/information" render={props => <Information {...props} />} />
</div>
</Router>
);
}
}
Dashboard.js
import React from 'react';
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div ><h1> Hello {this.props.match.params.user}</h1></div>
);
}
}
export default Dashboard;
I hope it will help you.
On RR4 you can use matchPath
const match = routes.find(route) => matchPath(props.location.pathname, {
path: route.path,
exact: true,
strict: false
})
Every react component is passed through the following function, found in node_modules ReactElement.js:
ReactElement.createElement = function (type, config, children){
.
.
.
}
This also Includes <Route> and <Router>. Consider the following JSX Code from a React Tutorial :
<Router history={hashHistory}>
<Route path="/" component={Layout}>
<IndexRoute component={Featured} />
<Route path="Settings" component={Settings} />
<Route path="Archives" component={Archives} />
</Route>
</Router>
The above code will be transpiled into the code shown below using Babel:
"use strict";
React.createElement(
Router,
{ history: hashHistory },
React.createElement(
Route,
{ path: "/", component: Layout },
React.createElement(IndexRoute, { component: Featured }),
React.createElement(Route, { path: "Settings", component: Settings }),
React.createElement(Route, { path: "Archives", component: Archives })
)
);
From this code, the "children" of the outer <Route> are the inner 3 <Route>s. We know that :
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
class MyContainer extends React.Component{
render(){
return (
.
.
//This will be MyFirstComponent or MySecondComponent
{this.props.children}
.
.
);
}
}
But the same is not true for the <Route>s. The value of this.prop.children for the JSX Router code applies to the component prop, but not the Router itself. Why is this behavior of this.props.children different for <Route> than any other ReactComponent?
Because Router deletes its Route children's children.
Here's the createRouteFromReactElement function from RouteUtils.js in react-router 3.0.1:
export function createRouteFromReactElement(element) {
const type = element.type
const route = createRoute(type.defaultProps, element.props)
if (route.children) {
const childRoutes = createRoutesFromReactChildren(route.children, route)
if (childRoutes.length)
route.childRoutes = childRoutes
delete route.children
}
return route
}
Notice the line fifth from the end: delete route.children.
Why does it do that? Consider this invariant warning from Route.prototype.render:
render() {
invariant(
false,
'<Route> elements are for router configuration only and should not be rendered'
)
}
I'm trying to set up server-side rendering with the newest version of react-router v.4. I followed this tutorial https://react-router.now.sh/ServerRouter.
I get following error when I refresh browser: Invariant Violation: React.Children.only expected to receive a single React element child.
my routes.jsx file:
export default () =>
<div>
<Header />
<Match pattern="/" component={Home} />
<Match pattern="/about" component={About} />
<Miss component={NotFound} />
</div>;
and in index.jsx I'm rendering app
import BrowserRouter from 'react-router';
import Routes from './routes';
ReactDOM.render(<BrowserRouter> <Routes /> </BrowserRouter>, document.getElementById('app'));
Now as server I'm using express.js. Here is my configuration:
import Routes from '../routes';
server.use((req, res) => {
const context = createServerRenderContext();
let markup = renderToString(
<ServerRouter location={req.url} context={context} > <Routes /> </ServerRouter>);
const result = context.getResult();
if (result.redirect) {
res.writeHead(301, {
Location: result.redirect.pathname,
});
res.end();
} else {
if (result.missed) {
res.writeHead(404);
markup = renderToString(
<ServerRouter location={req.url} context={context}> <Routes /> </ServerRouter>);
}
res.write(markup);
res.end();
}
});
I didn't find any tutorial for server-rendering with this version of react-routes except official.
Can anyone help me what I'm doing wrong ? thanks.
Solved !
First problem was that I had spaces around <Routes /> tag.
Correct solution:
<ServerRouter location={req.url} context={context}><Routes /></ServerRouter>);
Second problem was in included <Header /> tag in routes.jsx file.
I had the following error (Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of StatelessComponent)
File Header.jsx contained the following line of code:
import Link from 'react-router';
Correct solution: (I forgot to put curly brackets ):
import { Link } from 'react-router';
The big issue is that the<BrowserRouter> is only expected to have one child, so you should wrap it's children in a div. This is done so that React Router is environment agnostic (you can't render a div in React Native, so RR expects you to include the appropriate wrapper).
export default () =>
<BrowserRouter>
<div>
<Header />
<Match pattern="/" component={Home} />
<Match pattern="/about" component={About} />
<Miss component={NotFound} />
</div>
</BrowserRouter>;
As a secondary issue, you are including the <BrowserRouter> in your <App> component, so it will be rendered on the server. You do not want this. Only the <ServerRouter> should be rendered on the server. You should move the <BrowserRouter> further up your client side component hierarchy to avoid this.
// App
export default () =>
<div>
<Header />
<Match pattern="/" component={Home} />
<Match pattern="/about" component={About} />
<Miss component={NotFound} />
</div>;
// index.js
render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('app'))
because BrowserRouter doesn't exist on react-router, try install and import it from react-router-dom
I believe the answers listed above is outdated. As of today, the official react-router docs suggest using StaticRouter instead of ServerRouter for server side rendered apps.
A fantastic documentation can be found here.
For anybody coming later, Ryan Florence has added a snippet of how to accomplish this.
SSR in React Router v4
// routes.js
const routes = [
{
path: '/',
component: Home,
exact: true
},
{
path: '/gists',
component: Gists
},
{
path: '/settings',
component: Settings
}
]
// components
class Home extends React.Component {
// called in the server render, or in cDM
static fetchData(match) {
// going to want `match` in here for params, etc.
return fetch(/*...*/)
}
state = {
// if this is rendered initially we get data from the server render
data: this.props.initialData || null
}
componentDidMount() {
// if rendered initially, we already have data from the server
// but when navigated to in the client, we need to fetch
if (!this.state.data) {
this.constructor.fetchData(this.props.match).then(data => {
this.setState({ data })
})
}
}
// ...
}
// App.js
const App = ({ routes, initialData = [] }) => (
<div>
{routes.map((route, index) => (
// pass in the initialData from the server for this specific route
<Route {...route} initialData={initialData[index]} />
))}
</div>
)
// server.js
import { matchPath } from 'react-router'
handleRequest((req, res) => {
// we'd probably want some recursion here so our routes could have
// child routes like `{ path, component, routes: [ { route, route } ] }`
// and then reduce to the entire branch of matched routes, but for
// illustrative purposes, sticking to a flat route config
const matches = routes.reduce((matches, route) => {
const match = matchPath(req.url, route.path, route)
if (match) {
matches.push({
route,
match,
promise: route.component.fetchData ?
route.component.fetchData(match) : Promise.resolve(null)
})
}
return matches
}, [])
if (matches.length === 0) {
res.status(404)
}
const promises = matches.map((match) => match.promise)
Promise.all(promises).then((...data) => {
const context = {}
const markup = renderToString(
<StaticRouter context={context} location={req.url}>
<App routes={routes} initialData={data}/>
</StaticRouter>
)
if (context.url) {
res.redirect(context.url)
} else {
res.send(`
<!doctype html>
<html>
<div id="root">${markup}</div>
<script>DATA = ${escapeBadStuff(JSON.stringify(data))}</script>
</html>
`)
}
}, (error) => {
handleError(res, error)
})
})
// client.js
render(
<App routes={routes} initialData={window.DATA} />,
document.getElementById('root')
)