I built my website on ReactJS to be SPA. I am trying to use React Router v.5.2.0. I have a sample app called ToDoList.js that should be at the URL on the portfolio page is below. For some reason it works fine on my localhost, but not when I deploy it to Netlifly and hosted on my Github. It doesn't seem to work when it's live here. The back buttons on the browser also don't seem to work right either.
Portfolio.js:
<Link to="/portfolio/todo-list">here</Link>
The URL in the browser updates to the right URL, but it doesn't load the component. In my App.js file I have 2 types of routes so the sample app (todo list) doesn't show the top nav. It works
App,js:
function App() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<MainRoute exact path="/" component={Home} />
<MainRoute exact path="/about" component={About} />
<MainRoute exact path="/portfolio" component={Portfolio} />
<MainRoute exact path="/contact" component={Contact} />
<PortfolioRoute exact path="/portfolio/todo-list" component={ToDoList} />
</Switch>
</div>
</BrowserRouter>
);
}
export const MainRoute = ({ component: Component, ...rest}) => {
return(
<Route {...rest} component={(props) => (
<>
<TopNav />
<Container fluid>
<Row className="content-row">
<Component {...props} />
</Row>
</Container>
<Gradient />
</>
)} />
);
};
export const PortfolioRoute = ({ component: Component, ...rest}) => {
return(
<Route {...rest} component={(props) => (
<>
<Component {...props} />
</>
)} />
);
};
export default App;
All the endpoints along the /portfolio route need to be handled in one place, otherwise ReactRouter can't handle the nesting as it selects the Main one first.
See the docs' demo on nesting here
Also, so that directly linking to https://www.aubreyhlaverty.com/portfolio/todo-list works, you should add a 200 redirect to your Netlify _redirects file, like this. Might also help with your other issue.
/* /index.html 200
Related
Target
I'm using react router v6.
I have a parent route with a static header with a back button that when I click it needs to go one path above, exp. /shop/1/item/create to /shop/1/item, when i click the back button i call the function navigate from useNavigate()
example
Red is the root page, yellow is where the static header is and green is content that i want to change
Problem
When i call navigate regardless of using "." or ".." or "./" the page never change correctly, it either redirects from /shop/1/items/create to /shop or to /shop/1
the only way of redirecting correctly is using -1 but it causes the problem that if someone copy's the URL when going back they get redirected to whatever page they were before pasting the URL.
Code
const ShopPage = () => {
const [ state, setState ] = React.useState<IShopPageState>({ shop: { name: "" }, isInvalid: false })
const { id } = useParams()
const navigate = useNavigate()
return (
<div id="shop-page">
<Card>
<div id="shop-page-header">
<Icon canHover onClick={() => navigate("./")} icon="BiArrowBack"/>
<Title text={state.shop ? state.shop.name : ""}/>
</div>
</Card>
<div id="shop-page-content">
<Routes>
<Route path="/*" element={<div onClick={() => navigate('./items')}>wddw</div>}/>
<Route path="/items/*" element={<ItemsPage shop={state.shop}/>}/>
<Route path="/items/create" element={<ItemPage/>}/>
</Routes>
</div>
</div>
)
}
Here is the code of the yellow part with only the important stuff, the header is static and always visible and where the back button is.
I believe this code is enough to understand, the green part only redirects the url from /items/ to /items/create using navigate('./create')
I think a possible solution would be simply copying pasting the header for each page but i find that to be bad practice and it is a last resort solution
Here is a example of the problem
EDIT
As asked here is some extra code showing the problem
App.js
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="*" element={<Link to="/shop">Shop</Link>} />
<Route path="shop/*" element={<Shop />} />
</Routes>
</BrowserRouter>
);
}
Shop.js
const Shop = () => {
return (
<>
<div>
<Link to="./">Back</Link>
</div>
<Routes>
<Route path="*" element={<Link to="./items">All</Link>} />
<Route path="items/*" element={<Link to="./create">Item</Link>} />
<Route path="items/create" element={<>Create</>} />
</Routes>
</>
);
};
if you are using a nested route then better to use
<Route path="/" >
<Route index element={<Items/>} />
<Route path="item:id" element={<Item/>} />
</Route>
Hey Dear #pekira you can see just a simple hint in the code below for React Router v6
import { Routes, Route, Link } from 'react-router-dom';
const App = () => {
return (
<>
<h1>React Router</h1>
<nav>
<Link to="/home">Home</Link>
<Link to="/user">User</Link>
</nav>
<Routes>
<Route index element={<Home />} />
<Route path="home" element={<Home />} />
<Route path="user/:Id" element={<User />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</>
);
};
I'm trying to create a protected/private route with react-router-dom v5, as in the link here, I use only the code I needed - Protected routes and authentication with React Router v5.
Now I need both components private from each other
The problem: after Getting Home component transferred fromSignUp components I can go back to SignUp and thats not what I want.
Signup corrently is the first page (as sign-in). I don't want the user to go back to sigh-up components if he already signed up and present at the Home component.
The working website project is here - CodeSandbox
Anyone has idea how can I make both component protected/private?
GuardedRoute.jsx
function GuardedRoute({ children, auth, ...rest }) {
return (
<Route
{...rest}
render={() => {
return auth === true ? children : <Redirect to="/signup" />;
}}
/>
);
}
export default GuardedRoute;
App.js
const App = () => {
const [userID, setUserID] = useState('');
const [userSignedUp, setIfSignUp] = useState(false);
return (
<div className="App-div">
<GuardedRoute exact path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Switch>
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
</Switch>
</div>
);
};
export default App;
Please try any of your solutions at my codesandbox before posting your answer so we will not try solutions in theory only and in vain :)
You could make the signup route only exist if the user is not logged in, and then use a catch-all that will redirect to /home
<div className="App-div">
<Switch>
{!userSignedUp && (
<Route path="/signup">
<SignUp setUserNumber={setUserID} setIfSignUp={setIfSignUp} />
</Route>
)}
<GuardedRoute path="/home" auth={userSignedUp}>
<Home userIDNumber={userID} setIfSignUp={setIfSignUp} />
</GuardedRoute>
<Route path="/">
<Redirect to="/home" />
</Route>
</Switch>
</div>
Updated sample: https://codesandbox.io/s/jolly-https-t7dmj?file=/src/containers/App.js
or you could encapsulate the logic to another component like GuardedRoute, let say UnguardedRoute that will redirect somewhere if user is logged in.
I'm currently struggling using React and its library react-router-dom.
To simplify:
I've this in my App component:
const App = () => {
return (
<Router>
<Layout>
<Switch>
// <Other Routes />
<Route path='/product/:id' component={ProductScreen} />
// <Other Routes />
</Switch>
</Layout>
</Router>
)
}
The <ProductScreen /> (single product view) is wrapped like this:
The <ProductNav /> component embeds two links from react-router-dom. It also includes the required logic to reach previous or next product. Like so for instance:
<Link
className='prev'
to={`/product/${currentIndex - 1 < 0 ? ids[lastIndex] : ids[currentIndex - 1]}`}
>
Previous
</Link>
It's working but I'm not satisfied with that approach because the <ProductScreen /> unmounts each time I click 'previous' or 'next' and this leads to some unaesthetic things on the page. I would like to prevent it from unmounting in order to have this instead:
I tried many things, read the doc but I'm quite stuck right now. If anyone has any idea of how to achieve that I would be glad to read it.
You can use Route nested Route as following:
<Header />
<Main>
<Route path="/products" component={Product> />
</Main>
<Footer />
on page Product
const {url} = useRouteMatch();
render() {
<>
<Navbar />
<Route path=`${url}/:id` component={ProductDetail} />
</>
}
Recently I started learning React and my problem here is that I cannot hide Navbar when Im in Login page/component. I have a Router in index.js like this:
const routing = (
<div>
<NavBar />
<Router>
<div>
<Switch>
<Route exact path="/" component={App} />
<Route path="/users/:id" component={Users} />
<Route path="/users" component={Users} />
<Route path="/contact" component={Contact} />
<Route path="/login" component={LoginPage} hideNavBar={true} />
<Route component={Notfound} />
</Switch>
</div>
</Router>
</div>
)
ReactDOM.render(routing, document.getElementById('root'));
From the little search that I made most approaches were like inserting <NavBar /> in every component and use a flag to hide it when im in Login. Is there any other way like modifying the above code simple and fast?
Not really you could have restrict access to all other routes like below I suppose
const PrivateRoute = ({component: Component, ...rest }) =>
<Route
{...rest}
render={props =>
authenticated ? <Container ><Component {...props} /> <Container /> : <Redirect to="/login" />
}
/>
};
Where you container has a header , maybe footer and takes a child prop (your component)
There are better approaches for hiding the NavBaron authenticated routes, but if you want to hide it when it's on foo route, you could check the path name and decide to render it or not.
import { useLocation } from "react-router-dom";
const NavBar = () => {
const location = useLocation()
return location.pathname == '/login' ? <YourNavBarComponents /> : null
}
The best way to hide parts of the UI building with React is to not include the markup. In order to do NOT display parts relying on the authentication you should have a flag if the authentication has done and is successful.
For example you can build a component that share some context in order to check if the authentication flag is true and render the parts that must be available only to users who have logged in.
You can follow this example to see the details how to build that component.
Basically you have a component that wraps another component and based on some rules render its children or call a render prop:
function Private({ shouldRender = false, children }) {
return shouldRender
? children
: null;
}
function App() {
return (
<div>
<h2>Application</h2>
<Private><div>This part is hidden</div></Private>
<Private shouldRender={true}><div>This part is <strong>available</strong></div></Private>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Please read this properly before marking as duplicate, I assure you I've read and tried everything everyone suggests about this issue on stackoverflow and github.
I have a route within my app rendered as below;
<div>
<Header compact={this.state.compact} impersonateUser={this.impersonateUser} users={users} organisations={this.props.organisations} user={user} logOut={this.logout} />
<div className="container">
{user && <Route path="/" component={() => <Routes userRole={user.Role} />} />}
</div>
{this.props.alerts.map((alert) =>
<AlertContainer key={alert.Id} error={alert.Error} messageTitle={alert.Error ? alert.Message : "Alert"} messageBody={alert.Error ? undefined : alert.Message} />)
}
</div>
The route rendering Routes renders a component that switches on the user role and lazy loads the correct routes component based on that role, that routes component renders a switch for the main pages. Simplified this looks like the below.
import * as React from 'react';
import LoadingPage from '../../components/sharedPages/loadingPage/LoadingPage';
import * as Loadable from 'react-loadable';
export interface RoutesProps {
userRole: string;
}
const Routes = ({ userRole }) => {
var RoleRoutesComponent: any = null;
switch (userRole) {
case "Admin":
RoleRoutesComponent = Loadable({
loader: () => import('./systemAdminRoutes/SystemAdminRoutes'),
loading: () => <LoadingPage />
});
break;
default:
break;
}
return (
<div>
<RoleRoutesComponent/>
</div>
);
}
export default Routes;
And then the routes component
const SystemAdminRoutes = () => {
var key = "/";
return (
<Switch>
<Route key={key} exact path="/" component={HomePage} />
<Route key={key} exact path="/home" component={HomePage} />
<Route key={key} path="/second" component={SecondPage} />
<Route key={key} path="/third" component={ThirdPage} />
...
<Route key={key} component={NotFoundPage} />
</Switch>
);
}
export default SystemAdminRoutes;
So the issue is whenever the user navigates from "/" to "/second" etc... app re-renders Routes, meaning the role switch logic is rerun, the user-specific routes are reloaded and re-rendered and state on pages is lost.
Things I've tried;
I've tried this with both react-loadable and React.lazy() and it has the same issue.
I've tried making the routes components classes
Giving all Routes down the tree the same key
Rendering all components down to the switch with path "/" but still the same problem.
Changing Route's component prop to render.
Changing the main app render method to component={Routes} and getting props via redux
There must be something wrong with the way I'm rendering the main routes component in the app component but I'm stumped, can anyone shed some light? Also note this has nothing to do with react-router's switch.
EDIT: I've modified one of my old test project to demonstrate this bug, you can clone the repo from https://github.com/Trackerchum/route-bug-demo - once the repo's cloned just run an npm install in root dir and npm start. I've got it logging to console when the Routes and SystemAdminRoutes are re-rendered/remounted
EDIT: I've opened an issue about this on GitHub, possible bug
Route re-rendering component on every path change, despite path of "/"
Found the reason this is happening straight from a developer (credit Tim Dorr). The route is re-rendering the component every time because it is an anonymous function. This happens twice down the tree, both in App and Routes (within Loadable function), below respectively.
<Route path="/" component={() => <Routes userRole={user.Role} />} />
needs to be
<Routes userRole={user.Role} />
and
loader: () => import('./systemAdminRoutes/SystemAdminRoutes')
Basically my whole approach needs to be rethought
EDIT: I eventually fixed this by using the render method on route:
<Route path="/" render={() => <Routes userRole={user.Role} />} />
Bumped into this problem and solved it like this:
In the component:
import {useParams} from "react-router-dom";
const {userRole: roleFromRoute} = useParams();
const [userRole, setUserRole] = useState(null);
useEffect(()=>{
setUserRole(roleFromRoute);
},[roleFromRoute]}
In the routes:
<Route path="/generic/:userRole" component={myComponent} />
This sets up a generic route with a parameter for the role.
In the component useParams picks up the changed parameter und the useEffect sets a state to trigger the render and whatever busines logic is needed.
},[userRole]);
Just put the "/" in the end and put the other routes above it.
Basically it's matching the first available option, so it matches "/" every time.
<Switch>
<Route key={key} exact path="/home" component={HomePage} />
<Route key={key} path="/second" component={SecondPage} />
<Route key={key} path="/third" component={ThirdPage} />
<Route key={key} exact path="/" component={HomePage} />
<Route key={key} component={NotFoundPage} />
</Switch>
OR
<Switch>
<Route path="/second" component={SecondPage} />
<Route exact path="/" component={HomePage} />
<Route path="*" component={NotFound} />
</Switch>
Reorder like this, it will start working.
Simple :)