Multiple different layout alongside private route - javascript

I am trying to create a structure with multiple different layouts alongside private route to show the correct content based on user's log in status and assigned layout. Currently I have 3 different layouts but I may add another one in the future.
routes.js
import React from 'react';
import { LayoutOne, LayoutTwo, LayoutThree } from './layouts';
import RouteWithLayout from './components/RouteWithLayout/RouteWithLayout';
import Login from './components/Login/Login';
import Dash from './components/Dash/Dash';
import Home from './components/Home/Home';
import NotFound from './components/NotFound/NotFound';
import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary';
const Routes = () => (
<ErrorBoundary>
<Switch>
<RouteWithLayout
component={Home}
exact
layout={LayoutOne}
path="/"
isPrivate={false}
/>
<RouteWithLayout
component={Dash}
exact
layout={LayoutTwo}
path="/dash"
isPrivate={true}
/>
<RouteWithLayout
component={Login}
exact
layout={LayoutThree}
path="/login"
isPrivate={false}
/>
<Route component={NotFound}/>
</Switch>
</ErrorBoundary>
);
export default Routes;
RouteWithLayout.js
import React from 'react';
import { Route } from 'react-router-dom';
import { authService } from "./services/auth.service";
const RouteWithLayout = props => {
const { layout: Layout, component: Component, private: isPrivate, ...rest } = props;
const isLogged = authService.isLogged();
return (
<Route
{...rest}
render={matchProps =>
isPrivate ? (
isLogged ? (
<Layout>
<Component {...matchProps} />
</Layout>
) : (
<Redirect
to={{
pathname: "/login",
state: { from: matchProps.location }
}}
/>
)
) : (
<Layout>
<Component {...matchProps} />
</Layout>
)
}
/>
)
};
export default RouteWithLayout;
please lmk I am doing this the correct way or I should take some other/better approach so simplify what I have been trying to achieve ?

You have multiple options to work with different Layouts.
The approach you have taken is good if you have multiple Routes that share a common Layout.
However if you have a lot of varying Layouts for different Routes, its better to actually render the Layout inside individual components directly like
const Dash = () => (
<LayoutOne>
{/* Dash component code */}
</LayoutOne>
)
You could even adopt the above approach with common Routes too as its easier to use and let the Route component do what its actually doing.
P.S. Frameworks like Gatsby actually handle multiple layouts by using them within each Pages, so this is a good pattern to follow

Related

in Switch Route Routing time it's working, but now latest new Routes, Route it not working custom route

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.

React Dom Routes not working as intended, changes the path but nothing on the page

I am making a webplatform working with the OpenLibrary API, I am pulling data from the subjects then when you click on one of the books in the subjects, I want to be taken to a page called 'Book' where I display the data.
Now I have set up my Routes (i think they're correct), but when I click one of the items in the list, it doesn't do anything, it changes the URL to the route I set up, but it doesn't take me to the Book component where I display the data. Now, I haven't posted my Book.js file because it just has test code in it to see if I can get to the component. Any help will be appreciated!
Here are some snippets of my code:
App.js:
import React, { useState, useEffect } from "react";
import axios from "axios";
import Works from "./components/Works";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import "./styles.css";
function App() {
const [subjects, setSubjects] = useState([]);
const [worksDetails, setWorkDetails] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const url = "https://openlibrary.org/subjects/animals.json?limit=10";
useEffect(() => {
axios
.get(url)
.then((response) => {
setSubjects(response.data);
setWorkDetails(response.data.works);
setIsLoading(true);
})
.catch((error) => {
console.log(error);
});
}, []);
if (!isLoading) {
return <p>Loading...</p>;
} else {
return (
<>
<Navbar />
<div className="contentHeader">
<h1 className="subjectHeader" style={{ padding: "10px" }}>
Subject: {subjects.name.toUpperCase()}
</h1>
<h1 className="workHeader" style={{ padding: "10px" }}>
Work Count: {subjects.work_count.toLocaleString()}
</h1>
</div>
<div>
<Works works={worksDetails} />
</div>
<Footer />
</>
);
}
}
export default App;
Works.js
import React from "react";
import { Link } from "react-router-dom";
import Book from "../routes/Book";
const Works = (props) => {
return (
<div className="container">
<div>
<div className="heading">
{props.works.map((work) => {
return (
<Link to={`/book/${work.key}`} element={<Book />} key={work.key}>
<ul>{work.title}</ul>
</Link>
);
})}
</div>
</div>
</div>
);
};
export default Works;
Index.js
import { createRoot } from "react-dom/client";
import "./index.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./App";
import Book from "./routes/Book";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<BrowserRouter>
<Routes>
<Route path="*" element={<App />} />
<Route path="/book" element={<Book />}>
<Route path=":bookId" element={<Book />} />
</Route>
</Routes>
</BrowserRouter>
);
Issue
When you use
<Route path="/book" element={<Book />}>
<Route path=":bookId" element={<Book />} />
</Route>
The Route component is expecting Book to render an Outlet component for the nested Route component. This doesn't appear to be the UX you are going for though. The outer route is matched and rendered, but then because there's no outlet the inner route rendering the actual Book component you want with the param isn't rendered at all.
It turns out that in the fetched data that work.key isn't just an id, but a nested route value it seems, i.e. something like "key": "/works/OL138052W", so the UI is computing a malformed route ("/book//works/OL138052W") and linking to a route that doesn't exist.
Solution
to={`/book${work.key}`} is the path you are linking to, note the removal of the "/" between "book" and the injected work.key value:
<Link key={work.key} to={`/book${work.key}`}>
<ul>{work.title}</ul>
</Link>
Then the should render one of the following:
<Route path="/book/works/:bookId" element={<Book />} />
or
<Route path="/book/works">
<Route path=":bookId" element={<Book />} /> // "/book/works/:bookId"
</Route>
If you don't want to mess with this work.key value then you may want to select one of the other properties, so long as they provide sufficient uniqueness for identification purposes.
I believe your Route should look like this:
<Route path="/book/:bookId" element={<Book />}>

Pascal case error for dynamically adding component name to Reactjs Route's render method

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();
}

Integrating react-cookie with react redux

My main application is based on this old boilerplate which I have been slowly updating. Currently, all of the dependencies have been updated except for react-cookie.
I am trying to upgrade react-cookie to version 3.0.4 using this tutorial but I need some help overcoming some challenges I am facing during the transition process.
Following the tutorial, I changed index.js to
ReactDOM.render(
<CookiesProvider>
<Provider store={store}>
<App />
</Provider>
</CookiesProvider>,
document.querySelector('.wrapper'));
Now, my app.js file looks like this:
import React, { Component } from 'react'
import { withCookies } from 'react-cookie'
import Routes from '../Routes'
class App extends Component {
render() {
return (
<div className="container">
<Routes cookies={this.props.cookies} />
</div>
);
}
}
export default withCookies(App)
Now, my biggest concern comes here. My Routes component was never meant to be a Redux container so I changed it to this to accommodate the tutorial:
import React from 'react'
import { connect } from 'react-redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import ScrollUpButton from 'react-scroll-up-button'
// Import miscellaneous routes and other requirements
import NotFoundPage from './components/pages/not-found-page'
// Import Header and footer
import HeaderTemplate from './components/template/header'
import FooterTemplate from './components/template/footer'
// Import static pages
import HomePage from './components/pages/home-page'
// Import authentication related pages
import Register from './components/auth/register'
import Login from './components/auth/login'
import Logout from './components/auth/logout'
import ForgotPassword from './components/auth/forgot_password'
import ResetPassword from './components/auth/reset_password'
import ConfirmationMessage from './components/auth/confirmation_message'
import ResendVerificationEmail from './components/auth/resend_verification_email'
// Import dashboard pages
import Dashboard from './components/dashboard/dashboard'
import ChangePassword from './components/dashboard/profile/change-password'
// Import simulator pages
import Simulator from './components/simulator/index'
// Import higher order components
import RequireAuth from './components/auth/require_auth'
const BrowserRoutes = () => (
<BrowserRouter>
<div>
<HeaderTemplate logo="Stress Path Simulator" />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/forgot-password" component={ForgotPassword} />
<Route exact path="/reset-password/:resetToken" component={ResetPassword} />
<Route exact path="/confirmation-message" render={() => <ConfirmationMessage message="Please click on the link we sent to your email to verify your account." /> } />
<Route exact path="/resend-verification-email" component={ResendVerificationEmail} />
<Route exact path="/profile/change-password" component={RequireAuth(ChangePassword)} />
<Route exact path="/confirmation-password-changed" render={() => RequireAuth(<ConfirmationMessage message="Password has been successfully changed!" />)} />
<Route exact path="/simulator" component={RequireAuth(Simulator)} />
<Route exact path="/dashboard" component={RequireAuth(Dashboard)} />
<Route component={NotFoundPage} />
</Switch>
<FooterTemplate />
<ScrollUpButton />
</div>
</BrowserRouter>
);
const mapStateToProps = (state, ownProps) => {
return ({
state: state,
cookies: ownProps.cookies
});
}
export const Routes = connect(mapStateToProps, null)(BrowserRoutes)
export default Routes
I believe the problem essentially arises here. By doing so, I thought I would have been able to use the cookies from every single component like this:
//get this.props.cookies
const { cookies } = this.props;
//setting a cookie
cookies.set('name', 'Ross', { path: '/' });
//getting a cookie
cookies.get('name');
However, that doesn't seem the case and I cannot get cookies to work in any of my components especially in my actions/auth.js.
Does anyone have any suggestions? How can I efficiently use cookies in this scenario? I am assuming I can send down the cookies prop to each component that needs it but I am curious to find out what could be the best/cleanest way of using react-cookie with redux. I am fairly new to the MERN JavaScript software stack and mostly self-thought so I am a bit confused about some concepts. For example, if in Routes I am saving cookies into the redux's store, how can I access those cookies afterwards?
Instead of passing the cookies from the App/Router down, it is better to wrap only the components that will need the cookies. For example your Login component would look like this:
class Login extends React.Component {
render() {
let { cookies } = this.props;
let useCookie = cookies.get("testCookie");
...
}
}
export default withCookies(connect(mapStateToProps, null)(Login));

Mimic Wordpress Permalink Structure in a React Application

I'm using React to build the front-end of a site that uses Wordpress as a back-end. My goal is to mimic the most simple permalink structure in Worpdress, that is the site domain + the slug of the page or posts, without the addition of any /posts/ or /news/ or whatever. In other words, something like http://example.com/hello-world/for posts and http://example.com/about/ for pages.
I'm very new to React and I'm having some trouble understanding how to do that with React Router.
Here's what I have so far:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import MainHeader from './components/main-header/MainHeader';
import SinglePost from './pages/SinglePost';
const Layout = () => {
return (
<div>
<MainHeader />
<Switch>
<Route exact path="/" component={ HomePage } />
<Route exact path="/:slug" render={ props => <SinglePost { ...props } /> } />
<Route render={ () => { return( <p>Not Found</p> ) } } />
</Switch>
</div>
);
}
export default Layout;
This works fine for loading single posts with the permalink structure I've mentioned above, e.g. http://example.com/hello-world. The problem is, when I try to reach a page such as http://example.com/about, path="/:slug"is also matched, which means that the SinglePost component will be used for pages as well, while I want to use a different one.
How can I use different components for single posts and pages, while keeping the same permalink structure?
By the way, below is how I render my list of posts, with links to each individual post. Should I perhaps edit Link in some way?
import React from 'react';
import { Route, Link } from 'react-router-dom';
const ListPosts = ( props ) => {
let postsList = props.posts.map( ( post, index ) => {
return(
<article key={ index }>
<h2><Link to={ post.slug }>{ post.title.rendered }</Link></h2>
</article>
)
});
return (
<section className="posts">
<h1>Posts</h1>
{ postsList }
</section>
);
}
export default ListPosts;

Categories

Resources