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>
);
};
Related
I'm wondering how I can direct the user to the correct language path for their location upon loading the page in react?
I know I can detect their locaalized langauge with navigator.language. But then how do I use that to route them to a page immediately?
You could have a functional component as a intro component in your app that will read that location and route to the correct language using the useHistory hook like this:
import { useHistory } from "react-router-dom";
function HomePage() {
const history = useHistory();
React.useEffect(() => {
// Use navigator.language here
history.push("/en");
}, []);
return (
<div>
Loading...
</div>
);
}
You can always read more about it in the React Router docs and in the React Hooks docs.
You should read https://reactrouter.com/web/api/
navigator.language returns the browser's language,
and I assume that it is what you really want
I did it with basename that is one of BrowserRouter properties
import {
BrowserRouter,
NavLink,
Redirect,
Route,
Switch
} from "react-router-dom";
import "./styles.css";
export default function App() {
const lang = navigator.language.split("-")[0];
return (
<div className="App">
<BrowserRouter basename={lang}>
<Menu />
<Switch>
<Redirect exact from="/" to="/home" />
<Route path="/" exact component={Home} />
<Route path="/home" exact component={Home} />
<Route path="/about" exact component={About} />
<Redirect to="/home" />
</Switch>
</BrowserRouter>
</div>
);
}
export const Menu = () => {
return (
<>
<NavLink to="./home">Home</NavLink>
<span> / </span>
<NavLink to="./about">About</NavLink>
</>
);
};
export const Home = () => {
return <h1>Home</h1>;
};
export const About = () => {
return <h1>About</h1>;
};
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.
I am trying to figure out how to resolve an infinite loop issue.
FLOW: I have a state hook setUserData that I am passing to HomePage. From HomePage I am calling API to retrieve data and then set it in the App component.
Now when data is set and I click back button, then it into an infinite loop because userData is already set.
What can I do to fix it ?
CODE OF INTEREST:
{userData ?
<Redirect push
to={{
pathname: "/user",
search: `username=${userData.data.login}`
}}
/>
:
<Homepage setUserData={setUserData} />}
import React, { useState } from "react";
import "./App.css";
import Homepage from "./components/Homepage";
import ResultsPage from "./components/ResultsPage";
import Footer from "./components/Footer"
import { HashRouter, Switch, Route } from "react-router-dom";
import { Redirect } from "react-router-dom";
function App() {
const [userData, setUserData] = useState(null);
return (
<div className="App">
<HashRouter>
<Switch>
<Route exact path="/">
{userData ?
<Redirect push
to={{
pathname: "/user",
search: `username=${userData.data.login}`
}}
/>
:
<Homepage setUserData={setUserData} />}
</Route>
<Route path="/user">
<ResultsPage userData={userData} />
</Route>
</Switch>
</HashRouter>
{/* <Footer /> */}
</div>
);
}
export default App;
Base URL points to HomePage. When search is done on HomePage, data is passed to ResultsPage. (This is happening currently). Issue is that once userData is populated then clicking back redirects to ResultPage again. I need to be able to come back to HomePage when user clicks back button on home browser (Even if data is already loaded).
if u want achieved it, the better way is handle redirect in your homepage file
assume that
HomePage.jsx
import { useHistory} from "react-router-dom";
const HomePage = (props)=>{
const { setUserData } = props
....
const history = useHistory()
const handleClick = ()=>{
const data = HTTP.get('api')
setUserData(data)
history.push(`/user?username=${data}`)
}
...
return (
...
<button onClick={handleClick} >BUTTON</button>
...
)
}
App.jsx
function App() {
const [userData, setUserData] = useState(null);
return (
<div className="App">
<HashRouter>
<Switch>
<Route exact path="/">
<Homepage setUserData={setUserData} />}
</Route>
<Route path="/user">
<ResultsPage userData={userData} />
</Route>
</Switch>
</HashRouter>
{/* <Footer /> */}
</div>
);
}
I have a private route component which I use in my app.
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
const PrivateRoute = ({ component: Component, ...rest }) => {
const userLoggedIn = localStorage.getItem('token');
return (
<Route
{...rest}
render={(props) => (
userLoggedIn
? <Component {...props} />
: (
<Redirect to={{
pathname: '/login',
}}
/>
)
)}
/>
);
};
PrivateRoute.propTypes = {
component: PropTypes.elementType.isRequired,
};
export default PrivateRoute;
When logging in I'm setting token to localStorage and redirecting to PrivateRoute. The problem is that here userLoggedIn is null although if I check DevTools token is there in localStorage.
I'm not sure what to do here. I'm using localStorage so that when the page is refreshed user still logged in. All the help will be appreciated.
Parent of Private Route
const Routes = () => (
<>
<Provider store={store}>
<HeaderContainer />
<Switch>
<>
<PrivateRoute path="/" component={HomeContainer} exact />
</>
</Switch>
</Provider>
</>
);
export default Routes;
App.js
const App = () => (
<div className={styles.App}>
<Routes />
</div>
);
According to their documentation:
All children of a <Switch> should be <Route> or <Redirect> elements.
Only the first child to match the current location will be rendered.
It looks like your Private Route is wrapped in a React fragment, which could be causing the issue. Does moving the routes outside the fragment fix things?
<Switch>
{/* <> // remove line */}
<PrivateRoute path="/" component={HomeContainer} exact />
{/* </> // remove line */}
</Switch>
i want redirect to "/user". i write but this not work.
how to correctly redirect to the right page
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
<Redirect to="/user"/>
});
}
My router list. Among them there is a router with the path "/ user"
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
UPADATE
App.js
The button I click on is in the component <SearchForm/>
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
There are two ways to programmatically navigate with React Router - <Redirect /> and history.push. Which you use is mostly up to you and your specific use case.
<Redirect /> should be used in user event -> state change -> re-render order.
The downsides to this approach is that you need to create a new property on the component’s state in order to know when to render the Redirect. That’s valid, but again, that’s pretty much the whole point of React - state changes update the UI.
The real work horse of React Router is the History library. Under the hood it’s what’s keeping track of session history for React Router. When a component is rendered by React Router, that component is passed three different props: location, match, and history. This history prop comes from the History library and has a ton of fancy properties on it related to routing. In this case, the one we’re interested is history.push. What it does is it pushes a new entry onto the history stack - aka redirecting the user to another route.
You need to use this.props.history to manually redirect:
onClick = (e) => {
this.setState({ errorLoad: false});
getPlayerInfo(this.state.id).then(data => {
if(data.success == false) {
this.setState({ errorLoad: true});
return;
}
this.setState({ user: data.player});
console.log(data);
this.props.history.push('/user');
});
}
You should be getting history as a prop from your <Router> component.
EDIT:
Okay thank you for the code update. The SearchForm component is not nested under your BrowserRouter, so it is not getting the history prop. Either move that component inside the BrowserRouter or use the withRouter HOC in SearchForm reacttraining.com/react-router/web/api/withRouter
Option 1: Move SearchForm inside the BrowserRouter
render() {
let style = {marginLeft: '20px'};
return (
<div>
<Header source='https://www.shareicon.net/data/2017/02/15/878753_media_512x512.png'/>
<Centered style={ {marginTop: '50px'} }>
<BrowserRouter>
<SearchForm onClick={this.onClick} style={style} onChange={this.onHandle} placeholder="search"/>
<Switch>
<Route exact path='/' component={Startup} />
<Route path="/user" render={(props) => <User {...props} user={this.state.user} />} />
</Switch>
</BrowserRouter>
</Centered>
</div>
);
}
Option 2: use the withRouter HOC to inject the history prop into SearchForm manually:
import { withRouter } from 'react-router-dom';
class SearchForm extends React.Component { ... }
export default withRouter(SearchForm)