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;
Related
As an exercise, I'm making a react app (still learning React) that implements a login system with firebase. Of course, to implement such a feature, react router is necessary and I have successfully implemented it. However, once the user logs in he should be able to see a sidebar alongside other content that is changed dynamically. I now need to again use react router to change those pages when a user clicks on a specific item in the sidebar without having to render the sidebar with each component. I have read the docs for nesting routers but just cant get it to work. Any help is greatly appreciated.
Here's the code:
App.js:
import "./App.css";
import LoginForm from "./components/LoginForm";
import { AuthProvider } from "./contexts/AuthContext";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./components/Dashboard";
import PrivateRoute from "./components/PrivateRoute";
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Switch>
<PrivateRoute exact path="/" component={Dashboard} />
<Route path="/login" component={LoginForm} />
</Switch>
</AuthProvider>
</Router>
</div>
);
}
export default App;
Dashboard.js:
import React from "react";
import { useAuth } from "../contexts/AuthContext";
import { useHistory } from "react-router";
import Sidebar from "./Sidebar/Sidebar";
import { useRouteMatch } from "react-router";
const Dashboard = () => {
const { currentUser, logout } = useAuth();
const history = useHistory();
let { path, url } = useRouteMatch();
const handleLogout = async () => {
try {
await logout();
history.push("/login");
} catch (error) {
console.log(error);
}
};
if (!currentUser) return null;
return (
<div>
<Sidebar logout={handleLogout} />
</div>
);
};
export default Dashboard;
PS. I'm quite new to react and any tip/critique is welcome
You can always conditionally render the sidebar.
function Sidebar() {
const { currentUser } = useAuth()
if (!currentUser) return null
// ...
}
Within your App component, just render the Sidebar component outside of the Switch:
function App() {
return (
<div className="App">
<Router>
<AuthProvider>
<Sidebar />
<Routes />
</AuthProvider>
</Router>
</div>
);
}
function Routes() {
const { currentUser } = useAuth()
return (
<Switch>
{currentUser && <PrivateRoutes />}
<PublicRoutes />
</Switch>
)
}
Basically all you need to do is render the sidebar on all routes. If you need to render custom Sidebar content based off of routes, you can add another Switch within Sidebar. You can add as many Switch components as you want as long as they are within your Router.
Even though i understand what your trying to do, i don't think you should mind put the sidebar inside the component.
React is powerfull enough to cache a lots of stuffs and disable unnecessary renders. I think the path you should go its figure out how to use wisely useCallback useMemo, memo and make all the tricks to prevent re-renders inside the sidebar components. This way you can reuse the sidebarcomponent, or any component, without to think about location.
The footer must display a different CTA depending on if the user is on the Home page, or on the Product page. The product page is a single page that renders product information of the item selected on the home pages, and the way I have accomplished that is by using useParams(), and creating the a correlation between the page.id (comes from the data file ) and pageID.
I thought I could do the same thing for the footer, but I am getting an error claiming that everything is undefined, literally, everything.
This is my footer component. As you can see, I am importing the data for the footer from the data folder as "pageItems":
import React from 'react';
import { useParams } from 'react-router';
import pageItems from '../../data/footerData';
function Footer() {
const { pageID } = useParams();
const individualPage = pageItems.find(page => page.id === pageID);
return (
<div className="footer-container">
<p>{individualPage.copy}</p>
<a href="https://www.lego.com/kids" target="_blank">
<img src={individualPage.cta} alt="cta" />
</a>
</div>
);
}
The data file (which is just a javascript file) is structured in the following way:
const footerData = [
{
id: '1',
copy: 'You can find more fun at',
cta: HomeCta,
href: 'https://www.something.com',
},
];
I am sure I could create the correlation between <Route path="/:videoID"> (which works), and the footer, but I can't seem to get it right.
Edit: This is how my routing page is set up at the moment:
return (
<Router>
<Navbar />
<Switch>
<Route path="/:videoID">
<FeaturedVideo />
</Route>
<Route exact path="/">
<Home />
</Route>
</Switch>
<Footer />
</Router>
);
}
Any help will really help. thank you so much in advance!
And as a side note, I am using the react version that uses react hooks
The solution is to keep <Footer /> inside <Router>, but outside of the <Switch> and assign it as a component to a <Route> with as many path values as whereas you want it to display.
Here's a working version on codesandbox
import React from "react";
import "./styles.css";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from "react-router-dom";
const Footer = (props) => {
let { videoID } = useParams();
return <h3>Requested video ID: {videoID}</h3>;
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Router>
<Link to="/2">VideoID2 </Link>
<Link to="/3">VideoID3 </Link>
<Link to="/4">VideoID4 </Link>
<Switch>
<Route path="/:videoID">
<p>You are viewing</p>
</Route>
<Route exact path="/"></Route>
</Switch>
<Route exact path={["/", "/:videoID"]} component={Footer} />
</Router>
</div>
);
}
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
Really struggling with understanding how to create a button which routes to a new page (search.js).
I've tried different methods which some have worked in routing to the link but the page does not change (but I can see the url being changed to the search page).
*sorry at this point the code has been butchered with numerous attempts of adding the button
App.js -
```
import React, { useState } from "react";
import { BrowserRouter as Router, Link } from 'react-router-dom';
import Route from 'react-router-dom/Route';
function App() {
const [joke, setJoke] = useState()
const newJoke = () => {
fetch("http://api.icndb.com/jokes/random")
.then(result => result.json())
.then(result2 => {
console.log(result2)
setJoke(result2.value.joke)
})
return newJoke
}
return (
<Router>
<div className="jokeSection">
<h1>Chuck norris jokes</h1>
<h3>{joke}</h3>
<button onClick={() => newJoke()}>Click here for a chuckle</button>
<button onClick={() => { this.props.history.push("/search"); }}>Search!!</button>
</div>
<ul>
<li>
<Link to="/App">Home</Link>
</li>
<li>
<Link to="/search">Search</Link>
</li>
</ul>
</Router>
)
}
export default App;```
Search.js
import React from "react";
function Search() {
return (
<div>
<h2>Search</h2>
</div>
);
}
export default Search;
Ideally I want a button (just like the button onClick) to route to a new page - search.js.
I'm new to ReactJS and have tried to watch many tutorials but i'm having difficulty.
In your app.js file you need to have <Route> as children of <Router> not <Link>.
You can look over this code:
<Router>
<div>
<Route exact path="/">
<Home />
</Route>
<Route path="/news">
<NewsFeed />
</Route>
</div>
</Router>
The above code will create routes for your react application. Then you can navigate to each of them using <Link> in your Navbar rather than ordinary a tag.
I am trying to learn React and using Create-React-App to experiment.
Today I was trying to learn how to use React Router, but I couldn't make it work.
Here is my code: (App.js)
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom'
import { Navbar, Jumbotron, Button } from 'react-bootstrap';
class App extends Component {
render() {
const baseUrl = process.env.PUBLIC_URL;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">React Star Wars Table Test</h1>
</header>
<Router>
<div>
<NavLink to={baseUrl + '/Foo'}>Foo</NavLink> <NavLink to={'/Bar'}>Bar</NavLink>
<hr />
<Switch>
<Route path="/" exact render={() => (<h1>HOME</h1>)} />
<Route path={baseUrl + "/Foo"} exact Component={Foo} />
<Route path='/Bar' exact Component={Bar} />
</Switch>
</div>
</Router>
</div>
);
}
}
class Foo extends Component {
render() {
return (
<p>Foo!</p>
);
}
}
class Bar extends Component {
retnder(){
return (
<h1>Bar!</h1>
);
}
}
export default App;
The issue is that the routes don't display the components when they match the URL (either clicking on the NavLinks or manually typing the URL).
The base ('/') route works and displays the HOME H1.
I know the routes are matching because if I try to use the render attribute for all the routes, it works.
No compile errors, no console errors.
The sample code contains the Switch tag, but I have tried also
without, same result.
The sample code has a NavLink and a Route with
a baseUrl const and one without, I have tried either way (none, both,
one yes and one not), same result.
The prop of Route that takes a component is called component, not Component with a capital c.
<Route path='/Bar' exact component={Bar} />