I wanna open a popin inside a route, and I wanna add an hash to the url.
For example before onClick https://www.example.com/home after onClick https://www.example.com/home#send-me-an-email
Well it works but React Router rerender the whole route.
Am I doing something wrong with React Router ? Let's see my code below (I simplified the things)
index.jsx I am using BrowserRouter like everybody
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
const render = Component => {
ReactDOM.render(
<Router>
<Component />
</Router>,
document.getElementById('root')
);
};
render(App);
App.jsx I am using withRouter because I am gonna need history and location somewhere else
import React from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
import Header from './components/Header';
import Footer from './components/Footer';
import Home from './views/Home';
const App = ({ ...props }) => {
return (
<Header />
<section>
<Switch>
<Route exact path={"/"} component={() => <Home {...props} />} />
<Route path={"/home"} component={() => <Home {...props} />} />
</Switch>
</section>
<Footer />
);
};
export default withRouter(App);
Home.jsx unfortunately when I do this.props.history.push({ hash: 'send-me-an-email' }) it will rerender the route component Home, no good
...
render() {
<div>
<button onClick={() => this.props.history.push({ hash: 'send-me-an-email' })}>
Send me and email
</button>
<Popin
isOpened={this.state.isPopinOpened}
handleClose={() => this.props.history.push({ hash: '' })} />
</div>
}
...
How not to make a rerender just because I added a hash to te same url ? Cheers.
Simply rely on vanilla JS:
window.history.pushState("object or string", "Title", "/home#send-me-an-email");
This will add an hash/route without rendering or reloading anything.
I also wanted to add a #hash to the URL without triggering a re-render.
I simply used the useNavigate() hook from react-router-dom v6.
const navigate = useNavigate();
navigate('#my-fancy-hash');
Then, I read this hash with:
const { hash } = useLocation();
One thing to remember is that, at that point, the value of the hash variable would include the # character. When I had to actually use it, I simply stripped it out with:
hash.slice(1)
That should give you the proper value. I hope it helps!
Related
in Switch Route Routing time it's working, but now latest new Routes, Route it not working custom route
I have wrapped the navbar page and home page in HomeLayoutHOC
can anyone help me :) how to do this latest version I try but so many things. no result for this
I want 'HomeLayoutHOC " route instead of "Route"
->client\src\App.JSX :
//HOC
import HomeLayoutHOC from "./HOC/Home.Hoc";
import { Route, Routes } from "react-router-dom";
//Component
import Temp from "./Components/temp";
function App() {
return (
<>
<Routes>
<HomeLayoutHOC path="/" exact element={Temp} /> // <--- I want this to work!
// <Route path="/" element={<Temp />} /> // <-- this working fine
</Routes>
</>
);
}
export default App;
result 👇
screenshot!
->client\src\index.jsx :
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import "./index.CSS";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
-> client\src\HOC\Home.Hoc.jsx
import React from "react";
import { Route } from "react-router-dom";
// Layout
import HomeLayout from "../Layout/Home.Layout";
const HomeLayoutHOC = ({ component: Component, ...rest }) => {
return (
<>
<Route
{...rest}
component={(props) => (
<HomeLayout>
<Component {...props} />
</HomeLayout>
)}
/>
</>
);
};
export default HomeLayoutHOC;
->client\src\Layout\Home.Layout.jsx
import React from "react";
// Components
import Navbar from "../Components/Navbar";
const HomeLayout = (props) => {
return (
<>
<Navbar />
<div className="container mx-auto px-4 lg:px-20 ">{props.children}</div>
</>
);
};
export default HomeLayout;
please give me the possible suggestion for the latest router dom (Routes, Route)
wrapping/composing
How can I spread routeProps to make them available to your rendered Component the latest router dom (Routes, Route)
react-router-dom#6 removed the need, and compatibility, for custom route components. It's an invariant violation to render anything other than a Route or React.Fragment component in the Routes component. Custom route components are replaced with the use of either wrapper components on individual routes wrapping the element being rendered, or by layout route components that can wrap any number of nested Route components.
Wrapper components render the children prop
<Route
path="/"
element={(
<Wrapper>
<Componenet />
</Wrapper>
)}
>
Layout Route components render an Outlet component for nested routes to render their element prop into.
<Route element={<Layout />}>
<Route path="/" element={<Componenet />} />
</Route>
You are asking for the Layout Route version since it seems you want to render the Navbar component as part of a greater layout.
HomeLayout
import React from "react";
import { Outlet } from "react-router-dom";
import Navbar from "../Components/Navbar";
const HomeLayout = () => {
return (
<>
<Navbar />
<div className="container mx-auto px-4 lg:px-20 ">
<Outlet />
</div>
</>
);
};
export default HomeLayout;
App
Render HomeLayout on a pathless route as a Layout Route. The nested Route components render their content into the outlet.
import { Route, Routes } from "react-router-dom";
import HomeLayout from "./path/to/HomeLayout";
import Temp from "./Components/temp";
function App() {
return (
<Routes>
<Route element={<HomeLayout />}>
<Route path="/" element={<Temp />} />
... other routes to render with Home layout and Navbar ...
</Route>
... other routes to render without Home layout and Navbar ...
</Routes>
);
}
An important aspect you should notice here is that RRDv6 removed route props, all the old "props" are now only accessible via React hooks in the routed component, i.e. useNavigate, useLocation, useParams, etc. If you are still using React class-based components they won't be able to use React hooks, and since RRDv6 also no longer exports the withRouter Higher Order Component, well, you will need to roll your own. See What happened to withRouter? I need it! for details.
Hei, I am trying to build a simple react app with a navigation feature. The main theme is I have 3 components, App, Test, and AppShell. App component is the default(Initial) component. And what I want is that Every time user goes to App component, my app will redirect to Test component.
The problem I am facing is that my redirection only works when I load the application the first time, after that my redirection is not working.
I am sharing my three components code below along with the index page!
Index page
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Routes,
Route
} from "react-router-dom";
ReactDOM.render(
<Router>
<Routes>
<Route path='/' element={<App />} />
<Route path='/test' element={<Test />} />
</Routes>
</Router>,
document.getElementById('root')
);
function Test() {
return <h1>Test Me</h1>;
}
reportWebVitals();
App Component
import "./App.css";
import AppShell from "./components/core/appShell";
import { useNavigate } from 'react-router-dom';
export default function App(props) {
let navigate = useNavigate();
return <AppShell {...props} navigate={navigate} />;
}
App shell component
import React, { Component } from 'react';
import { Outlet } from "react-router-dom";
class AppShell extends Component {
componentDidMount() {
this.props.navigate('/test');
}
render() {
return (
<div>
<h1>This is app shell</h1>
<Outlet />
</div>
);
}
}
export default AppShell;
I thought the problem is lies within component hooks, so I tried to implement the redirection inside the constructor too, but nothing is working for me!
The basic business problem I am trying to solve here is - A user will be redirected to a login page, every time he/she tries to browse another page regardless of valid login(valid user) could be based on the valid token on local storage
Could anyone say, What I am doing wrong?
Authentication with regards to protected routes is actually pretty trivial in react-router-dom v6
Create a wrapper component that accesses the auth context (local state, redux store, local storage, etc...) and based on the auth status renders an Outlet component for nested routes you want to protect, or a redirect to your auth endpoint.
Example AuthWrapper:
const AuthWrapper = () => {
const location = useLocation();
const token = !!JSON.parse(localStorage.getItem("token"));
return token ? (
<Outlet />
) : (
<Navigate to="/login" replace state={{ from: location }} />
);
};
Uses:
useLocation hook to grab the current location user is attempting to access.
Outlet component for nested protected routes.
Navigate component for declarative navigation, sends the current location in route state so user can be redirected back after authenticating.
Example Usage:
<Router>
<Routes>
<Route element={<AuthWrapper />}>
<Route path="/" element={<App />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Router>
Login - In the authentication handler, once authenticated, set the localStorage and navigate to the location that was passed in route state.
function Login() {
const { state } = useLocation();
const navigate = useNavigate();
const { from = "/" } = state || {};
const login = () => {
localStorage.setItem("token", JSON.stringify(true));
navigate(from);
};
return (
<>
<h1>Test Me</h1>
<button type="button" onClick={login}>Log In</button>
</>
);
}
Whenever I console log props.match.params, I get an error:
TypeError: Cannot read property 'params' of undefined at App. I'm not sure this is relevant, but even if I console.log(props) I get four empty arrays.
Here is all the relevant code:
Home.js
import React from "react";
import App from "./App";
import { render } from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const Home = () => {
return (
<div>
<Router>
<Route exact path="/">
<App />
</Route>
<Route path="/:roomCode" component={App} />
</Router>
</div>
);
};
export default Home;
App.js (only the relevant part)
const App = (props) => {
console.log(props.match.params);
};
export default App;
const appDiv = document.getElementById("app");
render(<App />, appDiv);
I have been trying to figure this out for the past two days. Nothing works. Also, history.push also doesn't work, returns a very similar error. I have a feeling react-router-dom is broken in my project.
Help is much appreciated.
Edit:
Here is the codesandbox: https://codesandbox.io/s/reverent-microservice-iosu2?file=/src/App.js
Your Home Component is the root of all your components so it needs to be pass to render function not your App which is a descendent of Home.
after that change you need to change this line in your Home Component:
<Route exact path="/" render={(props) => <App {...props} />} />
import React from "react";
import App from "./App";
import { render } from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
const Home = () => {
return (
<div>
<Router>
<Route exact path="/" render={(props) => <App {...props} />} />
<Route path="/:roomCode" component={App} />
</Router>
</div>
);
};
export default Home;
here is how to render it:
const appDiv = document.getElementById("app");
render(<Home />, appDiv);
now you can get the props
const App = (props) => {
console.log(props.match.params);
};
export default App;
You could try using the React hooks provided by the React Router framework. There are several different hooks used to interact with the router.
const App = () => {
const { roomCode } = useParams()
console.log(params);
};
More info here
I am trying to get a component to render dynamically. The name for the component is getting pulled from the state. The name is in pascal case but still it throws a pascal case error. For instance, the value that I am trying on is "HeroBanner". If I put this directly, it works fine. If get this dynamically via state and then assigning it to a variable, it backfires with pascal case error.
Here's my code
import '#babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, withRouter} from 'react-router-dom'
import { createBrowserHistory } from 'history';
import HeroBanner from './IssueList.jsx';
import IssueTable from './IssueTable.jsx';
import {PropTypes} from 'prop-types';
import {Header} from './Header.jsx';
import Demo from './Cropcrop.jsx';
import {Receiver} from 'react-file-uploader';
const contentNode = document.querySelector("#contents");
const NoMatch = () => <p>404 no result</p>
class App extends React.Component{
constructor(){
super();
this.state = {
currentLocation: ""
}
this.changeLocation = this.changeLocation.bind(this)
}
changeLocation(e){
this.setState({
currentLocation: e
})
}
render(){
const Component= this.state.currentLocation === ''? "HeroBanner" : this.state.currentLocation
return(
<div>
<BrowserRouter>
<Header/>
<Redirect from="/" to="/selection"></Redirect>
<Switch>
<Route exact path={"/"+ Component} render={(props) => <Component />} />
<Route exact path="/selection" render={(props) => <IssueTable {...props} onChange={this.changeLocation} />} />
<Route path="/*" component={NoMatch}/>
</Switch>
</BrowserRouter>
<Component/>
</div>
)
}
}
const RoutedApp = () => (
<App/>
);
ReactDOM.render(<RoutedApp />, contentNode);
if(module.hot){
module.hot.accept();
}
You're not allowed to specify component dynamically like this in React (specifying a string instead of a type):
render={(props) => <Component />}
It should be something like:
const Component = this.state.currentLocation === '' ? HeroBanner : SomeOtherComponent;
I had a similar situation in the project I'm working on and ended up with creating a map between a certain string value (in my case it was the name of a tab, coming from the URL) and component's type:
const componentsMap = {
home: HomeComponent,
about: AboutComponent,
...
}
const Component = componentsMap[myStringValue];
That last piece of code is allowed as you're refering to the component's type, not to some ordinary string which can't be used as valid JSX.
As for routing, I noticed you just set some string value in the state and expect routing to happen. This is not the case as react-router-dom listens for changes in the browser's url/path and you should probably perform manual routing in your case to move to another <Route> view. In the example below I perform navigation in the onDropdownChange handler upon dropdown change.
The way I like to minimize repetition of strings representing route names and component names is to have the dynamic value as param in the URL. Then a <Subroute> component handles all the mapping for that group of routes (you might not need that additional level of nesting, depends on size of app). In the sandbox I created you can see the map I was talking about in the comments. You select the appropriate component based on the tab param in the URL (which holds the value selected from the dropdown).
Example here
EDIT for a question in the comments (11 Aug 2020):
You can also use a neat trick to customize what gets rendered by a route, like this (without having to use render of Route):
<Route exact path={path}>
<ComponentOne {...someProps} />
<ComponentTwo {...someOtherProps} />
<p>Some html too</p>
</Route>
To use render instead (maybe less readable, I prefer the first option, just pass the necessary JSX, wrapped in Fragment if needed):
<Route
exact
render={routeProps => (
<Fragment>
<ComponentOne {...someProps} />
<ComponentTwo {...someOtherProps} />
<p>Some html too</p>
</Fragment>
)}
/>
While the above answer is absolutely correct. Here's how I followed the above the logic.
import '#babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter, Route, Switch, Redirect, withRouter} from 'react-router-dom'
import { createBrowserHistory } from 'history';
import HeroBanner from './IssueList.jsx';
import IssueTable from './IssueTable.jsx';
import {PropTypes} from 'prop-types';
import {Header} from './Header.jsx';
import Demo from './Cropcrop.jsx';
import {Receiver} from 'react-file-uploader';
const contentNode = document.querySelector("#contents");
const NoMatch = () => <p>404 no result</p>
const componentList={
HeroBanner: HeroBanner
}
class RoutedApp extends React.Component{
constructor(){
super();
this.state = {
currentLocation: ""
}
this.changeLocation = this.changeLocation.bind(this)
}
changeLocation(e){
console.log(e)
this.setState({
currentLocation: e
})
}
render(){
const Component= this.state.currentLocation === '' ? NoMatch : componentList[this.state.currentLocation]
console.log(Component)
return(
<div>
<BrowserRouter>
<Header/>
<Redirect from="/" to="/selection"></Redirect>
<Switch>
<Route exact path={"/"+ this.state.currentLocation} component={Component} />
<Route exact path="/selection" render={(props) => <IssueTable {...props} onChange={this.changeLocation} />} />
<Route path="/*" component={NoMatch}/>
</Switch>
</BrowserRouter>
</div>
)
}
}
ReactDOM.render(<RoutedApp />, contentNode);
if(module.hot){
module.hot.accept();
}
I am having a bit of an issue with React Router that I can not seem to figure out. It does not go back to the very last page visited, rather the first page it loaded. Here is an example.
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import PrivateRoute from './utils/auth/PrivateRoute';
<Router>
<PrivateRoute exact path="/dashboard" component={DashboardView} />
<PrivateRoute exact path="/games" component={Games} />
<PrivateRoute
exact
path="/viewgame/:id*/"
component={SingleGameView}
/>
</Router>
When you go to /dashboard, you can click to view a games list that takes you to /games. You can then click on a game to see a single view of it, which takes you to /viewgame/:id*
Like so: /dashboard -> /games -> /viewgame/:id*
When you click on a game and are taken to /viewgame/, and then decide to click back in the browser, it takes me back to /dashboard instead of taking me back to /games. It is skipping over the last visited page, and taking me back to the first loaded page. I can send someone back to a route by setting up my own 'click to go back' button, but I need the browsers actual back and forward button to do this.
PrivateRoute is a HOC I setup to check to make sure the user accessing the route is authenticated or not. Otherwise they are booted. In case that could be the issue here is that component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
//Utils - Auth
import { userAuth } from '../../../authentication/authentication';
const { isAuthenticated } = userAuth;
//Checks if a user isAuthenticated. If so, it renders the passed in secure component. If not, it renders a redirect to /signin
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute;
Here’s a snapshot of the PrivateRoute props when it’s rendered a component:
You can achieve this by calling goBack() function in history object inside withRouter().
import React from 'react';
import { withRouter } from 'react-router-dom'
export default withRouter(({ history }) => {
return (
<div>
<button onClick={() => history.goBack()}>BACK</button>
</div>
)
});
You can simply use the useHistory hook from react-router-dom
import { useHistory } from "react-router-dom";
const history = useHistory();
...
<div onClick={ ()=>history.goBack() }>Back </div>
Remove 'exact' from your routes props.