I have an app that looks like this:
<BrowserRouter>
<Route path="/" component={Root} />
</BrowserRouter>
// Root.js
function Root() {
return (
<MemoryRouter>
<Switch>
<Route path="/" component={MemRoot} />
<Route path="/bar">
<Link to="/>To browser Root</Link>
</Route>
</MemoryRouter>
);
}
Right now when I click on Link, it's going to change the URL of MemoryRouter, but I'd actually like it to mutate the URL of BrowserRouter.
How can I tell Link to target the BrowserRouter?
Alright, you can do this. See the demo:
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter,
MemoryRouter,
Route,
Switch,
Link
} from 'react-router-dom';
const Root = props =>
<BrowserRouter>
<Route path="/" render={ () =>
<div>
<p>Home</p>
<button
onClick={ props.onOpen }>
Start Wizard
</button>
</div> } />
</BrowserRouter>;
const MemRoot = props =>
<div>
<p>Wizard Home</p>
<button onClick={ props.onClose }>
Finish Wizard
</button>
<p>
<Link to='/bar'>/bar</Link>
</p>
</div>;
const Wizard = ({ onClose }) =>
<MemoryRouter>
<Switch>
<Route path="/bar" render={ () =>
<div>bar <Link to='/'>/</Link></div> } />
<Route exact path="/" render={ () =>
<MemRoot onClose={ onClose }/> } />
</Switch>
</MemoryRouter>;
const App = () =>
<BranchRoute />
class BranchRoute extends Component {
state = { out: false }
handleClose = () => this.setState({ out: true })
handleOpen = () => this.setState({ out: false })
render () {
const { out } = this.state;
return (
<div>
{ out
? <Root onOpen={ this.handleOpen }/>
: <Wizard onClose={ this.handleClose } />
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Related
I am trying for this component redirect to HomePage but after it is redirected to the home page it restarts the data within.
Any suggestions to replace window.location.href = "/HomePage"?
import React, { useEffect } from "react";
const Thankyou = () => {
useEffect(() => {
setTimeout(() => {
window.location.href = "/HomePage"
}, 10000)
}, [])
return (
<div className={"movie-container"}>
<h2>
Thank you for ordering...<br/>
</h2>
<h2>Page will redirect in 10 seconds...</h2>
</div>
)
}
export default Thankyou;
These are the Routes for the App.tsx
const App: FC = () => {
return (
<>
<Header/>
<Routes>
<Route path="/" element={<Navigate replace to='/HomePage' />} />
<Route path="/HomePage" element={<HomePage />} />
<Route path="/FavoritePage" element={<FavoritePage />} />
<Route path="/MoviePage/movie/:id" element={<MoviePage />} />
<Route path="/Thankyou" element={<Thankyou />} />
</Routes>
</>
)
};
This is the index.tsx
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<GalleryProvider>
<App />
</GalleryProvider>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
When you mutate window.location.href you end up reloading the app. This effectively remounts the entire app so any React state will be reset.
Use the navigate function to issue an imperative redirect.
import React, { useEffect } from "react";
import { useNavigate } from 'react-router-dom';
const Thankyou = () => {
const navigate = useNavigate();
useEffect(() => {
const timer = setTimeout(() => {
navigate("/HomePage", { replace: true });
}, 10000);
return () => {
clearTimeout(timer); // <-- clear timeout effect on unmount
};
}, [])
return (
<div className="movie-container">
<h2>Thank you for ordering...</h2>
<h2>Page will redirect in 10 seconds...</h2>
</div>
);
};
export default Thankyou;
I managed to make a private route and navigate to different pages using react-router-dom. How ever, when I navigate to a page and reload it, it first goes to /login for half a second and the reloads the page correctly. How can I prevent this unwanted behavior and improve my routing?
Here are my routes:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
This is the full component:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
This is my Login component.
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "#material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
This is the App component:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "#material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
Also, here is the auth logic:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
Finally, the full code is in https://gitlab.com/programandoconro/adminkanjicreator
Any suggestion will be appreciated, thanks.
I would recommend doing the auth check earlier. So something like this so that the routes themselves only get rendered if there is something in auth. I think your example is also missing the Switch statement which often helps.
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
Typically you will want some sort of "loading" or "indeterminant" state to represent neither authenticated nor unauthenticated. You can use this third "state" to hold the UI before committing to rendering one way or the other on anything based upon authentication.
Since your auth logic resolves to a boolean true|false.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
You can use the fact that the initial auth state is neither of these. I suggest using null.
const [auth, setAuth] = useState<string | null>(null);
When rendering the Route utilizing the auth state you can augment the logic to return early before deciding to redirect.
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
Note here that I've also switched over to the render prop, the component prop is intended for attaching actual React components. These are treated a little differently. You can read about the route render method differences here.
The full router example:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
Note here that I've also included the Switch component and reordered the routes so the more specific paths are listed before less specific paths. This allows you to remove the unnecessary exact prop from all the routes since the Switch renders routes exclusively (versus inclusively as the Router does).
I finally managed to solve the issue. Now the reload works perfectly and the security was implemented as excepted. This is my final Router:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
This is how the component looks like now.
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
Thanks #Richard and #Drew for their kind support.
i using react-router for my project,
in that there's a problem which is for every route "#" sign is added at the start of every router path..
ex": http://localhost:3000/#/login
i want to remove that # sign but i couldn't able solve it my self.
procedure of my routing is
in app.js im checking the user is signed in or not if not signed in then he will redirect into /login page.(for that also it is showing path as http://localhost:3000/#/login)
below is the app.js
import React, { Component, Fragment } from "react";
import { HashRouter, Route, Switch, Redirect } from "react-router-dom";
// import { renderRoutes } from 'react-router-config';
import "./App.scss";
import { connect } from "react-redux";
import { loadUser } from "./actions/authActions";
const loading = () => (
<div className="animated fadeIn pt-3 text-center">Loading....</div>
);
// Containers
const DefaultLayout = React.lazy(() =>
import("./containers/DefaultLayout/DefaultLayout")
);
// Pages
const Login = React.lazy(() => import("./views/Login/Login"));
const Register = React.lazy(() => import("./views/Register/Register"));
const Page404 = React.lazy(() => import("./views/Page404/Page404"));
const Page500 = React.lazy(() => import("./views/Page500/Page500"));
class App extends Component {
componentDidMount() {
this.props.LOADUSER();
}
render() {
return (
<HashRouter>
<React.Suspense fallback={loading()}>
<Switch>
{!this.props.isAuthenicated ? (
<Fragment>
<Redirect from="*" to="/login" />
<Route
exact
path="/login"
name="Login Page"
render={props => <Login {...props} />}
/>
{/* <Route
exact
path="/register"
name="Register Page"
render={(props) => <Register {...props} />}
/>
<Route
exact
path="/404"
name="Page 404"
render={(props) => <Page404 {...props} />}
/>
<Route
exact
path="/500"
name="Page 500"
render={(props) => <Page500 {...props} />}
/> */}
</Fragment>
) : (
<Route
name="Home"
path="/"
render={props => <DefaultLayout {...props} />}
/>
)}
</Switch>
</React.Suspense>
</HashRouter>
);
}
}
const mapStateToProps = state => ({
isAuthenicated: state.auth.isAuthenicated,
isLoading: state.auth.isLoading,
error: state.error,
token: state.auth.token
});
const mapDispachToProps = dispach => {
return {
//LOGIN: (newUser) => dispach(login(newUser)),
LOADUSER: () => dispach(loadUser())
};
};
export default connect(mapStateToProps, mapDispachToProps)(App);
else he is signed in then im using a component called DefaultLayout Component I will render it.
it has all the routes for other usages which is using routes from routes.js.
below is the DefaultLayout Component
import React, { Component, Suspense } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import * as router from "react-router-dom";
import { Container } from "reactstrap";
import { logout } from "../../actions/authActions";
import { ToastContainer } from "react-toastify";
import Loader from "react-loaders";
import "react-toastify/dist/ReactToastify.css";
import {
AppHeader,
AppSidebar,
AppSidebarFooter,
AppSidebarForm,
AppSidebarHeader,
AppSidebarMinimizer,
AppBreadcrumb2 as AppBreadcrumb,
AppSidebarNav2 as AppSidebarNav
} from "#coreui/react";
// sidebar nav config
import _navs from "../../_nav";
// routes config
import routes from "../../routes";
import { connect } from "react-redux";
const DefaultHeader = React.lazy(() => import("./DefaultHeader"));
class DefaultLayout extends Component {
state = {
isAuthenicated: true
};
loading = () => <Loader type="ball-triangle-path" />;
signOut(e) {
e.preventDefault();
this.props.history.push("/login");
this.props.LOGOUT();
}
render() {
return (
<div className="app">
<AppHeader fixed>
<Suspense fallback={this.loading()}>
<DefaultHeader onLogout={e => this.signOut(e)} />
</Suspense>
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<Suspense>
<AppSidebarNav
navConfig={_navs}
{...this.props}
router={router}
/>
</Suspense>
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
<main className="main">
<AppBreadcrumb appRoutes={routes} router={router} />
<Container fluid>
<Suspense fallback={this.loading()}>
<Switch>
{routes.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
render={props => (
<route.component {...props} {...route.props} />
)}
/>
) : null;
// (
// <Redirect from="*" to="/dashboard" />
// );
})}
<Redirect from="*" to="/" />
</Switch>
</Suspense>
<ToastContainer autoClose={3000} position="bottom-center" />
</Container>
</main>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
isAuthenicated: state.auth.isAuthenicated,
error: state.error
});
const mapDispachToProps = dispach => {
return {
LOGOUT: () => dispach(logout())
};
};
export default connect(mapStateToProps, mapDispachToProps)(DefaultLayout);
example of routes.js also below
const routes =[{
path: "/",
exact: true,
name: "Home",
component: Dashboard
},
{
path: "/user_overview",
name: "Users Overview",
component: Register
}]
for every route it's showing # can anyone help me to resolve that # sign in the route path?
Thank you!
You are using HashRouter this is the purpose of this Router.
Usually one uses it in-order to prevent the server to get those routes.
If you want to use real routes just replace it with BrowserRouter.
Pay attention that your server will need to be able to support those routes.
navigate to some route say /some/page press reload, make sure that your server return your client code.
I'm looking for a way to do some route protection with react-router-4. Looking at an example in the documentation, they create a Component which is rendered like this:
<PrivateRoute path="/protected" component={Protected} />
and the privateRoute component:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
I don't really understand why I should want to pass the "Protected" component as a property and then have to take care of spreading all the ...props and ...rest.
Before I was reading this doc (and other example), I created the following code, which just nest the routes in another component which takes care of the authentication part.
Because my example (which seems to work perfectly well), looks way more simplistic, I must be missing something.
Are there any downsides on this approach?
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import Nav from './Nav';
// dummy
const Auth = {
isAuthenticated: () => { return true; }
}
const Home = () => <h1>Home</h1>
const SignIn = () => <h1>SignIn</h1>
const About = () => <h1>About</h1>
class PrivateOne extends Component {
render() {
console.log(this.props);
return <h1>Private</h1>
}
}
const PrivateTwo = () => <h1>PrivateTwo</h1>
const PrivateThree = () => <h1>PrivateThree</h1>
const NotFound = () => <h1>404</h1>
const Private = ({isAuthenticated, children}) => {
return(
isAuthenticated ? (
<div>
<h1>Private</h1>
{children}
</div>
) : (
<Redirect to={{
pathname: '/sign_in',
}}/>
)
)
}
const App = () =>
<div>
<Router>
<div>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/sign_in" component={SignIn} />
<Private isAuthenticated={Auth.isAuthenticated()}> {/* or some state later on */}
<Route path="/private1" component={PrivateOne} />
<Route path="/private2" component={PrivateTwo} />
<Route path="/private3" component={PrivateThree} />
</Private>
<Route component={NotFound} />
</Switch>
</div>
</Router>
</div>
export default App;
I have a simple enough react app. I have two buttons/actions that redirect. sign out and add. signout is working but add is not.
before add click
history.location '/'
location '/'
after add click
history.location '/add'
location '/add'
but related component doesnt render.
router.js
let appHistory = createHistory();
const appRouter = () => (
<Router history={appHistory}>
<Switch>
<Route path="/signin" component={SignInUp} exact={true} />
<Route path="/" component={Landing} />
<Route path="/add" component={CreateEvent} />
<Route path="/eventview" component={EventDetails} />
</Switch>
</Router>
)
Main component
import React, {Component} from 'react';
import RequireAuth from './RequireAuth';
import {startSignOut} from '../actions/auth';
import {fetchEvents} from '../actions/event';
import {connect} from 'react-redux';
import {withRouter} from 'react-router';
import EventItem from './EventItem';
import Header from './Header';
const EventDisplay = class EventDisplay extends Component {
componentDidMount = () => {
this.props.fetchEvents();
}
handleAddEvent = () => {
console.log(this.props);
this.props.history.push('/add');
}
handleSignOut = () => {
this.props.startSignOut();
}
render() {
return (
<div>
<Header signOut={this.handleSignOut}/>
{
this.props.events.map((event, ind) => {
return <EventItem key={ind} history={this.props.history} index={ind + 1} event={event}/>
})
}
<button onClick={() => this.handleAddEvent()}>+</button>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
startSignOut: () => startSignOut(dispatch),
fetchEvents: (userId) => dispatch(fetchEvents(userId))
});
const mapStateToProps = (state) => ({
events: state.events
})
const connectedWithRouter = withRouter(connect(mapStateToProps, mapDispatchToProps)(RequireAuth(EventDisplay)));
export default connectedWithRouter;
Header.js
const Header = (props) => {
return (
<div>
<h2>Eventio</h2>
<span>circle</span>
<span>user</span>
<button onClick={() => props.signOut()}>Sign out</button>
</div>
)
}
export default Header;
Your Route with path / will be used for any path that is not /signin. Give it an exact prop and it will only be used for path /.
const appRouter = () => (
<Router history={appHistory}>
<Switch>
<Route exact path="/" component={Landing} />
<Route path="/signin" component={SignInUp} />
<Route path="/add" component={CreateEvent} />
<Route path="/eventview" component={EventDetails} />
</Switch>
</Router>
)