React Router Route only render first route from config - javascript

I created a route config for react-router-dom following the official docs right here
But when I tried to implement my code, it only renders my first route from the config.
routes.js
import TellerLoginPage from '../TellerLoginPage';
import TellerMenuPage from '../TellerMenuPage';
export const routeConfig = [
{
path: '/teller/login',
exact: true,
component: TellerLoginPage,
},
{
path: '/teller',
exact: true,
component: TellerMenuPage,
},
];
I created a component to wrap the logic of implementing the props into the Route component from react-router-dom.
RouteItem.js
import React from 'react';
import { Route } from 'react-router-dom';
const RouteItem = ({ route }) => {
console.log(route);
return (
<Route
path={route.path}
exact={route.exact}
render={(props) => <route.component {...props} />}
/>
);
};
export default RouteItem;
App.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import RouteItem from '../../components/RouteItem';
import { routeConfig } from './routes';
function App() {
const populateRoute = () => {
const jsx = [];
routeConfig.forEach((r, i) => {
jsx.push(<RouteItem route={r} key={i} />);
});
return jsx;
};
return (
<Router>
// ...
<Switch>
{populateRoute()}
</Switch>
// ...
</Router>
)
}
What happened is that the only routes correctly rendered is the one mentioned first in the routes.js array.
But when I tried to render it directly in the App.js, the routes are correctly rendered.
Direct Render - App.js
import React from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import RouteItem from '../../components/RouteItem';
import { routeConfig } from './routes';
function App() {
return (
<Router>
// ...
<Switch>
{routeConfig.map((r, i) => {
return <Route path={r.path} render={(props) => <r.component {...props} key={i} />} />;
})}
</Switch>
// ...
</Router>
)
}
Is there anything that I missed lol I feel so stupid at this point

As #patrick-evans mentioned in the comment
...you are rendering your own component RouteItem directly in the Switch instead of Route directly. Switch might be internally trying to use something like the children prop to access each route...
At the the official example, they use spread syntax (<RouteWithSubRoutes key={i} {...route} />) instead of pass route as route prop: <RouteItem route={r} key={i} />). Therefore Switch can access to route props in the example, but can't in your code.
You should just fix <RouteItem route={r} key={i} />) to <RouteItem {...r} key={i} />) and it should work. And of course change your own component to const RouteItem = (route) => {...

Related

Why is state being lost in my app when passed via history.push()?

I've created the following very simple React app to simulate the behavior I'm seeing in my production app.
Root component:
import {
BrowserRouter as Router,
Route,
RouteComponentProps,
Switch,
} from "react-router-dom";
import { Home } from "./home";
import { SubRoute } from "./sub-route";
export default function Root(props) {
return (
<Router>
<Switch>
<Route path="/" exact>
<Home {...props} />
</Route>
<Route path="/sub-route" exact>
<SubRoute />
</Route>
</Switch>
</Router>
);
}
Home component:
import { LocationDescriptor } from "history";
import * as React from "react";
import { useHistory } from "react-router-dom";
import { TestLocationState } from "./sub-route";
export const Home = () => {
const history = useHistory();
return (
<>
<h1>Home</h1>
<button
onClick={() => {
const location: LocationDescriptor<TestLocationState> = {
pathname: "/sub-route",
state: {
name: "STATE PAYLOAD",
},
};
history.push(location);
}}
>
Pass state to sub route
</button>
</>
);
};
SubRoute component:
import * as React from "react";
import { useLocation } from "react-router-dom";
export type TestLocationState = {
name: string;
};
export const SubRoute = () => {
const { state } = useLocation<TestLocationState | undefined>();
return (
<>
<h1>Sub Route</h1>
<div>state: {JSON.stringify(state)}</div>
</>
);
};
In the dummy app, when I click the button in the Home component which calls history.push(), passing a location object with a state property, the state is not only successfully passed to the SubRoute component, but if I refresh or hard refresh, the state value is still available.
In my production app (a completely separate app that includes the above in addition to, of course a lot of other stuff), state is successfully passed to the SubRoute component, but it is not retained upon refresh. It is undefined.
I'm very confused as to what could be causing different behavior in my production app versus my test app. Is it a React Router thing? Thoughts?
It turns out that another developer on the team had added this line of code in part of the application that was wiping out the state:
https://github.com/preactjs/preact-router#redirects

How To Translate React Router Function to Typescript?

I Start Work On React Project used react-router-dom and the client need to convert project code to Typescript
I Build 2 folder "RouteWrapper.js" and "ProviderRoutes.js"
1- "RouteWrapper.js"
import React from 'react';
import PropTypes from 'prop-types';
import { Route, Redirect } from 'react-router-dom';
export default function RouteWrapper({
component: Component,
isPrivate,
...rest
}) {
const signed = false;
if (!signed && isPrivate) {
return <Redirect exact to="/signin" />;
}
if (signed && !isPrivate) {
return <Redirect to="/" />;
}
return (
<Route {...rest} component={Component} />
);
}
RouteWrapper.propTypes = {
isPrivate: PropTypes.bool,
component: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
.isRequired,
};
RouteWrapper.defaultProps = {
isPrivate: false,
};
2- "ProviderRoutes.js"
import React from 'react';
import { Switch } from 'react-router-dom';
// Components
import Route from './RouteWrapper';
import Authentication from '../Layouts/Authentication/Authentication.Layout';
import ChatApplication from '../Layouts/ChatApplication/ChatApplication.Layout';
export default function ProviderRoutes() {
return (
<Switch>
<Route exact path={["/signin", "/signup", "/reset-password"]} component={Authentication} />
<Route path="/" component={ChatApplication} isPrivate />
</Switch>
)
}
I believe the issue is mostly in RouteWrapper. First, let’s assume you’ll no longer want to use
prop types, since types are now checked at compile time by TypeScript.
These are the basics:
React is exported using module.exports = React. This means the technically correct way to import
React is import * as React from 'react';. Alternatively you can set "esModuleInterop": true in
tsconfig.json.
The return type of React components is React.ReactElement.
Also it’s ok to use destructuring to assign simple default props in functional components.
For <RouteWrapper /> specifically component is extracted from props, and then passed to
<Route /> along with the other props. This means it might as well be included in the rest props.
Since the rest props are passed to <Route />, the type of props for <RouteWrapper /> should
extend RouteProps from react-router-dom.
RouteWrapper would look like this:
import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
interface RouteWrapperProps extends RouteProps {
isPrivate: boolean;
}
export default function RouteWrapper({
isPrivate = false,
...rest
}: RouteWrapperProps): ReactElement {
const signed = false;
if (!signed && isPrivate) {
return <Redirect exact to="/signin" />;
}
if (signed && !isPrivate) {
return <Redirect to="/" />;
}
return <Route {...rest} />;
}
If you really want to use propTypes or defaultProps, type safety can be added using React.FC.
import * as PropTypes from 'prop-types';
import * as React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
interface RouteWrapperProps extends RouteProps {
isPrivate: boolean;
}
const RouteWrapper: React.FC<RouteWrapperProps> = ({ isPrivate, ...rest }) => {
const signed = false;
if (!signed && isPrivate) {
return <Redirect exact to="/signin" />;
}
if (signed && !isPrivate) {
return <Redirect to="/" />;
}
return <Route {...rest} />;
};
RouteWrapper.propTypes = {
isPrivate: PropTypes.bool,
};
RouteWrapper.defaultProps = {
isPrivate: false,
};
export default RouteWrapper;
Beware the preferred way of writing routes using react-router is to use children, not the
component prop.
import * as React from 'react';
import { Switch } from 'react-router-dom';
// Components
import RouteWrapper from './RouteWrapper';
import Authentication from '../Layouts/Authentication/Authentication.Layout';
import ChatApplication from '../Layouts/ChatApplication/ChatApplication.Layout';
export default function ProviderRoutes(): React.ReactElement {
return (
<Switch>
<Route exact path={['/signin', '/signup', '/reset-password']}>
<Authentication />
</Route>
<Route isPrivate path="/">
<ChatApplication />
</Route>
</Switch>
);
}
This should help you get started converting the rest of the application. :)

React Authentication using HOC

The server sends a 401 response if the user is not authenticated and I was trying to check for authentication in the front end using a HOC as seen in Performing Authentication on Routes with react-router-v4.
However, I am getting an error saying TypeError: Cannot read property 'Component' of undefined in RequireAuth
RequireAuth.js
import {React} from 'react'
import {Redirect} from 'react-router-dom'
const RequireAuth = (Component) => {
return class Apps extends React.Component {
state = {
isAuthenticated: false,
isLoading: true
}
async componentDidMount() {
const url = '/getinfo'
const json = await fetch(url, {method: 'GET'})
if (json.status !== 401)
this.setState({isAuthenticated: true, isLoading: false})
else
console.log('not auth!')
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router-dom';
import SignIn from './SignIn'
import NavigationBar from './NavigationBar'
import LandingPage from './LandingPage'
import Profile from './Profile'
import Register from './Register'
import { RequireAuth } from './RequireAuth'
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<NavigationBar />
<Switch>
<Route exact path = '/'
component = {LandingPage}
/>
<Route exact path = '/register'
component = {Register}
/>
<Route exact path = '/profile'
component = {RequireAuth(Profile)}
/>
<Route path="*" component = {() => "404 NOT FOUND"}/>
</Switch>
</Router>
</div>
);
}
}
export default withRouter(App);
I can think of some possibilities:
------- Moved this to top which eventually fixed OP's issue -------
Try remove the curly braces at {React},
import React from 'react';
------- Moved this to top which eventually fixed OP's issue -------
In RequireAuth.js, Try
const RequireAuth = ({ Component }) => {} // changed from Component to { Component }
In App.js, use Component start with capital letter
<Route exact path = '/' Component = {LandingPage}/>
Also, in <Route path="*" component = {() => "404 NOT FOUND"}/>, looks like you're not passing in a React component because the function is not returning a JSX (I can't test now so I'm not very sure, though).
try this instead:
() => <div>404 NOT FOUND</div>
or if it doesn't work, define a functional component externally and pass into the Route:
const NotFoundComponent = () => <div>404 NOT FOUND</div>
<Route path="*" component = {NotFoundComponent}/>
try to do it like this:
const RequireAuth = ({ component: Component }) => {}

Component cannot listen to react-router

I have a component that is used persistently across my spa. I want it to be aware of my router and the various paths that my spa is on. Is there an easy way to do this, or do I have to bandaid some redux (or something similar) state solution that is always listening to my router changes? Thanks! You can see the below for an example.
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Navigation from './navigation';
const UserReport = () => <h2>User Report</h2>;
const UserPage = () => <h2>User Page</h2>;
const Routes = () => (
<React.Fragment>
<Route component={Navigation} />
<Switch>
<Route exact path="/users/:startDate" component={UserReport} />
<Route exact path="/users/:userId" component={UserPage} />
</Switch>
</React.Fragment>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
navigation.jsx:
import React from 'react';
import { withRouter } from 'react-router-dom';
const Navigation = (props) => {
console.log(props.match.path);
// expected: "/users/:startDate"
// received: "/"
return (
<h2>Navigation</h2>
);
};
export default withRouter(Navigation);
Since the Navigation route doesn't have any path specified, it always matches whatever path you're on but the match.path only shows you the minimum path required to match for the navigation. That's why it's always /.
You can use location.pathname but it gives you the matched value and not the matched path.
const Navigation = props => {
console.log(props.location.pathname);
// prints `/users/1` if you're on https://blah.com/users/1
// prints `/users/hey` if you're on https://blah.com/users/hey
return <h2>Navigation</h2>;
};
Not sure that's what you want but if you expand what exactly you're trying to achieve, maybe I can help more.
Moreover, your second route to path="/users/:userId" overshadows the first route. Meaning there is no way to tell if hey in /users/hey is startDate or userId. You should introduce a separate route like path="/users/page/:userId".
I ended up using this react-router github discussion as my solution.
An example of my implementation:
index.jsx:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import { Route, Switch } from 'react-router-dom';
import { history, store } from './redux/store';
import Layout from './layout';
const home = () => <h2>Home Page</h2>;
const users = () => <h2>Users</h2>;
const userPage = () => <h2>User Page</h2>;
const layoutRender = component => route => <Layout component={component} route={route} />;
const Routes = () => (
<Switch>
<Route exact path="/" component={layoutRender(home)} />
<Route exact path="/users" component={layoutRender(users)} />
<Route exact path="/users/:id" component={layoutRender(userPage)} />
</Switch>
);
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes />
</ConnectedRouter>
</Provider>, document.getElementById('app'),
);
layout.jsx:
import React from 'react';
const Layout = (props) => {
const {
component: Component,
route,
} = props;
return (
<div>
<h1>This is the layout</h1>
<Component route={route} />
</div>
);
};
export default Layout;

React Router Error: You should not use Route outside of Router

I'm just doing some basic routing in my react app and I've done it this way before so I'm pretty confused to as why it isn't working now.
The error I am getting says: You should not use <Route> or withRouter() outside a <Router>
I'm sure this is super basic so thanks for baring with me!
import React from 'react'
import { Route } from 'react-router-dom'
import { Link } from 'react-router-dom'
import * as BooksAPI from './BooksAPI'
import BookList from './BookList'
import './App.css'
class BooksApp extends React.Component {
state = {
books: []
}
componentDidMount() {
this.getBooks()
}
getBooks = () => {
BooksAPI.getAll().then(data => {
this.setState({
books: data
})
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={() => (
<BookList
books={this.state.books}
/>
)}/>
</div>
)
}
}
export default BooksApp
You need to setup context provider for react-router
import { BrowserRouter, Route, Link } from 'react-router-dom';
// ....
render() {
return (
<BrowserRouter>
<div className="App">
<Route exact path="/" render={() => (
<BookList
books={this.state.books}
/>
)}/>
</div>
</BrowserRouter>
)
}
Side note - BrowserRouter should be placed at the top level of your application and have only a single child.
I was facing the exact same issue. Turns out that i didn't wrap the App inside BrowserRouter before using the Route in App.js.
Here is how i fixed in index.js.
import {BrowserRouter} from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
document.getElementById('root')
);

Categories