Reputation: 647
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
Upvotes: 4
Views: 7594
Reputation: 11848
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
Upvotes: 2