Passing props to PrivateRoute Component - reactjs

Hi I have a react (GatsbyJs) app where i am using dynamically fetched data and authentication. I have a PrivateRoute component that checks whether the user is logged in and then redirects to the component or to the login page depending on the status.
I now need to pass props to components but couldn't master it.
Here is the PrivateRouter:
import React from 'react'
import { navigate } from 'gatsby'
import { isLoggedIn } from '../services/auth'
const PrivateRoute = ({ component: Component, location, ...rest }) => {
if (!isLoggedIn() && location.pathname !== '/app/login') {
navigate('/app/login')
return null
}
return <Component {...rest} />
}
export default PrivateRoute
And the app.js code:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import OrderDetails from "../modules/order-details"
import ItemDetails from "../modules/item-details"
import ProductionOrders from "../modules/production-orders"
import ProdOrderDetail from "../modules/production-order-detail"
import CardDetail from '../modules/card-details'
import Cards from "../modules/cards"
import Orders from "../modules/orders"
import Items from "../modules/items"
import PrivateRoute from '../components/privateRoute'
import Profile from '../components/profile'
import Login from '../modules/login'
import ForgotPassword from "../modules/forgotPassword"
import NewPassword from "../modules/newPassword"
import Invoices from "../modules/invoices"
import Dispatches from "../modules/dispatches"
import InvoiceDetails from "../modules/invoice-details"
import OrderPlan from "../modules/order-plan"
import AccountStatementPage from "../modules/acc-statement"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<PrivateRoute path="/order-details/:orderId" component={OrderDetails} />
<PrivateRoute path="/item-details/:itemId" component={ItemDetails} />
<PrivateRoute path='/production-orders' component={ProductionOrders} />
<PrivateRoute path="/production-order-detail/:companyNr/:orderId" component={ProdOrderDetail} />
<PrivateRoute path="/cards" component={Cards} />
<PrivateRoute path="/card-details/:cardId" component={CardDetail} />
<PrivateRoute path="/orders" component={Orders} />
<PrivateRoute path="orders/cId/:cId" component={Orders} />
<PrivateRoute path="orders/keyword/:keyword" component={Orders} />
<PrivateRoute path="/items" component={Items} />
<PrivateRoute path="/items/keyword/:keyword" component={Items} />
<Login path="/login" />
<ForgotPassword path="/forgot-password" />
<NewPassword path="/new-password" />
<PrivateRoute path="/profile" component={Profile} />
<PrivateRoute path="/invoices/" component={Invoices}/>
<PrivateRoute path="/invoices/cId/:cId" component={Invoices}/>
<PrivateRoute path="/dispatches/" component={Dispatches}/>
<PrivateRoute path="/dispatches/cId/:cId" component={Dispatches}/>
<PrivateRoute path="/invoice-details/:invId" component={InvoiceDetails} />
<PrivateRoute path="/order-plan" component={OrderPlan} />
<PrivateRoute path="/acc-statement/:id" component={AccountStatementPage}/>
</Router>
</Layout>
)
}
export default App
How should I rework them to be able to pass down props to the Component?
Thanks in advance.
P.S.
This is the component:
import React from 'react'
import Container from "react-bootstrap/Container"
import Col from 'react-bootstrap/Col'
import Row from 'react-bootstrap/Row'
import AccStatement from '../components/accStatement'
const AccountStatementPage = (props,{location}) => {
console.log(location)
console.log(props)
return (
<Container fluid>
<h1>Cari Hesap Ekstresi</h1>
<Row className="h-100">
<AccStatement id={props.id} />
</Row>
</Container>
)
}
export default AccountStatementPage

Your private route component is already configured to pass on any additional props passed to it from the router, but since you are trying to send additional data to the rendered component you need to send the route state correctly.
Link
<Link to=“...” myState=“....” >
You can access route state from the location prop passed to each rendered component.
const MyComponent = ({ location }) => {
return (
<div>My route state: {location.state.myState}</div>
);
};
If your component doesn't receive the route props then you can use the useLocation react hook.
const MyComponent = () => {
const location = useLocation();
return (
<div>My route state: {location.state.myState}</div>
);
};
How should I access it in the target component?
location should be injected into the props object passed to your component by the Route. It isn't a separate argument. The route params are also placed on the match prop and are not a root-level prop value.
Given
<PrivateRoute
path="/acc-statement/:id"
component={AccountStatementPage}
/>
Component
const AccountStatementPage = ({ location, match }) => {
useEffect(() => {
// Log route state and match param `id` on mount
console.log(location.state, match.id);
}, []);
return (
<Container fluid>
<h1>Cari Hesap Ekstresi</h1>
<Row className="h-100">
<AccStatement id={match.id} />
</Row>
</Container>
)
};

You're really close.
This is working for me. Pass your state object inside Link per the Gatsby spec. Then you need to access your Router props (which contains the location object which has your Link state) and pass location.state.example to a component like so.
Link
<Link
className="link-wrapper"
to={endpoint}
state={{ example: 'something' }}
>
RouterPage (PrivateRouter)
const RouterPage = (
props: { pageComponent: JSX.Element } & RouteComponentProps
): JSX.Element => {
if (!loggedIn) {
navigate('/404')
}
return props.pageComponent
}
Router
const App: React.FC<RouteComponentProps> = (props: any) => {
return (
<Router className="below-header-container">
<RouterPage
path={withPrefix('/some-path/create')}
pageComponent={<CreateSomething />}
/>
<RouterPage
path={withPrefix('/some-path/:id')}
pageComponent={
<EditSomething name={props.location.state.example} />
}
/>
<RouterPage
path={withPrefix('/some-path')}
pageComponent={<Something />}
/>
</Router>
)
}
Component
const EditSomething: React.FC<any> = ({ name }) => {
return (
<section className="edit-something">
<div className="row">
<h1>{name}</h1>
</div>
</section>
)
}

In your PrivateRoute component you are destructuring the component as a Component (to render it), a location, and the rest (...rest) for rest of the props.
To pass a custom prop to your component inside PrivateRoute, you just need to access directly your prop:
In your app.js:
<PrivateRoute path="/order-plan" component={OrderPlan} customProp={`hello`}/>
Your customProp is passing to your component through rest spread operator in PrivateRoute so, in your component, just access to props.customProp.
If you want to pass something using Link component, you should use the state provided by Gatsby Link itself (because Gatsby extends from #reach/router):
<Link
to={`/order-plan`}
state={{ yourVariable: 'hello' }}
>
Then, on your order-plan page you have to access to props.location.state.yourVariable to get your data.

I couldn't access the route state through location. It was undefined all the time.
But I could access the route state through:
window.history.state.customProps
Thanks for your support, be well.

Related

React Routes not correctly rendering components [duplicate]

I'm using MUIv5 and React-Router v6 in my project, in which I want to wrap a layout around my pages, but the pages aren't rendering and all i'm getting is an empty div
This is my App component:
import React from "react";
import { Routes, Route } from "react-router-dom";
import { CssBaseline } from "#mui/material";
import MainLanding from './routes/MainLanding';
import StoreLanding from "./routes/StoreLanding";
import Layout from "./components/Layout";
const App = () =>{
return(
<>
<CssBaseline/>
<Routes>
<Route element={<Layout/>}>
<Route path="/" element={<MainLanding/>}/>
<Route path="/store" element={<StoreLanding/>}/>
</Route>
</Routes>
</>
)
}
export default App
This is the layout component where i'm calling the children via props:
import React from 'react';
const Layout = ({ children }) => {
return (
<div>
{children}
</div>
)
}
export default Layout;
Output:
A layout component should render an Outlet for nested Route components to be rendered into. This is different from wrapper components that consume and render the children prop.
See Layout Routes and Outlet for more details.
import { Outlet } from 'react-router-dom';
const Layout = () => {
return (
<div>
<Outlet /> // <-- nested routes rendered here!
</div>
)
};
For comparison, wrapper components are used to wrap a child component in the element prop. Example:
<Routes>
<Route
path="/"
element={(
<Layout>
<MainLanding />
</Layout>
)}
/>
<Route
path="/store"
element={(
<Layout>
<StoreLanding />
</Layout>
)}
/>
</Routes>

For different route, same react component does not get mounted

I am having an issue when using same component for two different routes, where i am expecting that that component gets destroyed and than get mounted again, but that does not happen:
When i change from /page1 to /page2 by clicking on the button Change to /page2 output in the console should be:
COMPONENT DISMOUNTED
COMPONENT MOUNTED
This means that MyComponent should be destroyed after path changes. This is important because i rely on the fact that change of the path gives me fresh component. I don't want to reset states and other hooks to default values manually.
Codesadnbox example
Is there a React problem or perhaps React router one?
App component
import {
Routes,
Route,
BrowserRouter,
Navigate
} from 'react-router-dom';
const App = () => {
return (
<BrowserRouter>
{/* Routes */}
<Routes>
{/* Route 1 */}
<Route path="/page1" element={<MyComponent someProp="value1" />} />
{/* Route 2 */}
<Route path="/page2" element={<MyComponent someProp="value2" />} />
<Route path="/*" element={<Navigate to={{ pathname: '/page1' }} />} />
</Routes>
</BrowserRouter>
);
};
MyComponent
import type { FunctionComponent } from 'react';
import { useEffect } from 'react';
import {
useNavigate
} from 'react-router-dom';
const MyComponent: FunctionComponent<{ someProp: string }> = ({ someProp }) => {
const history = useNavigate();
const onRouteChange = (route: string) => {
history(route);
};
useEffect(() => {
console.log('COMPONENT MOUNTED');
return () => {
console.log('COMPONENT DISMOUNTED');
};
}, []);
return (
<div>
<button onClick={() => onRouteChange('/page1')}>Change to /page1</button>
<button onClick={() => onRouteChange('/page2')}>Change to /page2</button>
<div>{someProp}</div>
</div>
);
};
React is actually doing its job correctly, since Route component returns same component with changed prop someProp. In any other case where i have a component where i change prop to it, this would happen again.
There is no obvious way to find this out unless you stumble upon this problem. Although thinking in the way React works, this should be obvious.
SOLUTION
Simple key should be added to both MyComponent components. In this way, React will know, because of the different key, that new component returned by Route differs.
Codesandbox to the solution
const App = () => {
return (
<BrowserRouter>
{/* Routes */}
<Routes>
{/* Route 1 */}
<Route
path="/page1"
element={<MyComponent key="/page1" someProp="value1" />}
/>
{/* Route 2 */}
<Route
path="/page2"
element={<MyComponent key="/page2" someProp="value2" />}
/>
<Route path="/*" element={<Navigate to={{ pathname: "/page1" }} />} />
</Routes>
</BrowserRouter>
);
};

React Pass history to a component defined in the Router

I have this Router in my App.js:
<Router basename={process.env.REACT_APP_ROUTER_BASE || '/MyApp'}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={Login} />
<Route path="/editProject" /*render={(props) => <ProjectEdit {...props} history={props.history} />}*/ component={ProjectEdit} />
{/*<Redirect path="*" to="/" />*/}
</Switch>
</Router>
From HomePage component I'm using < ProjectsList> component which have < Project> components.
Within the < Project> component I have an option of the menu for Editing a project and I'm trying to use there:
<OverflowMenuItem itemText="Edit" href="#" onClick={ () => this.props.history.push('/editProject')}/>
But I'm getting that props is undefined!
Resolution:
I passed the props.history as a prop history= {this.props.history} in this order:
HomePage -> ProjectsList -> Project
If you are working with a Functional Component, you can simply access the history instance object within your component using the useHistory hook, as stated on the React-Router documentation.
import { useHistory } from 'react-router-dom';
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
If you are working with a Class Component, you can wrap your component with withRouter, and then access the history object within your component
import { withRouter } from 'react-router';
class YourComponent extends React.Component {
render() {
const { history } = this.props;
return <OverflowMenuItem itemText="Edit" href="#" onClick={ () => history.push('/editProject')}/>;
}
}
export default withRouter(YourComponent);

React router dom redirect problem. Changes url, does not render component

Problem: When I use history.push(), I can see that browser changes url, but it does not render my component listening on the path. It only renders if I refresh a page.
App.js file:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Provider } from "react-redux";
import PropTypes from "prop-types";
//Components
import LoginForm from "../LoginForm/LoginForm";
import PrivateRoute from "../PrivateRoute/PrivateRoute";
import ServerList from "../ServerList/ServerList";
const App = ({ store }) => {
const isLoggedIn = localStorage.getItem("userToken");
return (
<Router>
<Provider store={store}>
<div className="App">
{isLoggedIn !== true && (
<Route exact path="/login" component={LoginForm} />
)}
<PrivateRoute
isLoggedIn={!!isLoggedIn}
path="/"
component={ServerList}
/>
</div>
</Provider>
</Router>
);
};
App.propTypes = {
store: PropTypes.object.isRequired
};
export default App;
Inside my LoginForm, I am making a request to an API, and after doing my procedures, I use .then() to redirect my user:
.then(() => {
props.history.push("/");
})
What happens: Browser changes url from /login to /, but component listening on / route is not rendered, unless I reload page.
Inside my / component, I use useEffect() hook to make another request to API, which fetches data and prints it inside return(). If I console.log inside useEffect() it happens twice, I assume initial one, and when I store data from an API inside component's state using useState() hook.
EDIT: adding PrivateRoute component as requested:
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({ component: Component, isLoggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
isLoggedIn === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/login" }} />
)
}
/>
);
};
export default PrivateRoute;
What I tried already:
1) Wrapping my default export with withRouter():
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginForm));
2) Creating custom history and passing it as prop to Router.
react-router-dom version is ^5.0.1. react-router is the same, 5.0.1
You have at two mistakes in your code.
You are not using <switch> component to wrap routes. So all routes are processed at every render and all components from each <route> are rendered.
You are using local store to exchange information between components. But change in local store is invisible to react, so it does not fire component re-rendering. To correct this you should use local state in App component (by converting it to class or using hooks).
So corrected code will look like
const App = ({ store }) => {
const [userToken, setUserToken] = useState(localStorage.getItem("userToken")); // You can read user token from local store. So on after token is received, user is not asked for login
return (
<Router>
<Provider store={store}>
<div className="App">
<Switch>
{!!userToken !== true && (
<Route exact path="/login"
render={props => <LoginForm {...props} setUserToken={setUserToken} />}
/>
)}
<PrivateRoute
isLoggedIn={!!userToken}
path="/"
component={ServerList}
/>
</Switch>
</div>
</Provider>
</Router>
);
};
And LoginForm should use setUserToken to change user token in App component. It also may store user token in local store so on page refresh user is not asked for login, but stored token is used.
Also be sure not to put anything between <Switch> and </Switch> except <Route>. Otherwise routing will not work.
Here is working sample

Nesting routes along with redux integration not working?

I'am nesting routes in my project. I have App.js in which I have defined the routes and inside the component I have more routes which I want them to be nested. The only problem is that my nested route is in the component which is connected to redux. The nested routing is not working properly.
I have already tried it from the official documentation but it does not work.
https://reacttraining.com/react-router/core/guides/philosophy
App.js
import { connect } from "react-redux";
import { withRouter, Route } from "react-router-dom";
function HowItWorks() {
return (
<div>
<h2 style={{ margin: 20 }}>How It Works</h2>
</div>
);
}
function AboutUs() {
return (
<div>
<h2 style={{ margin: 20 }}>About Us</h2>
</div>
);
}
class App extends React.Component {
render() {
return (
<div>
<Route path="/" exact component={HowItWorks} />
<Route path="/howitworks" exact component={HowItWorks} />
<Route path="/aboutus" component={AboutUs} />
<Route path="/admin" component={AdminContainer} />
</div>
);
}
}
Below is my Redux Container file which gets called based on the route specified in App.js. Also my App.js file may get connected to redux in the future by the connect() method.
AdminContainer.js
import { connect } from "react-redux";
import MainDesktopComponent from "../components/Admin/MainDesktopComponent";
const mapStateToProps = state => ({});
const mapDispatchToProps = dispatch => {
return {};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MainDesktopComponent);
MainDesktopComponent.js
I have tried this i.e giving the nested route inside Switch and many different ways but it is not working. Also note that I also want to pass props to the Dashboard component which will come from the above redux container component through mapstatetoprops.
import React from "react";
import Dashboard from "./Dashboard";
import { Route } from "react-router-dom";
import { Switch } from "react-router";
function MainDesktopComponent(props) {
return (
<div>
<Switch>
<Route
exact
path="/admin/dashboard"
render={props => {
<Dashboard/>;
}}
/>
</Switch>
</div>
);
}
export default MainDesktopComponent;
I'm not sure but what about try this?
<Switch>
<Route
exact
path="/admin/dashboard"
render={cProps => <Dashboard {...cProps} {...props} />}
/>
</Switch>
return Route render component.

Resources