React Router nested route - reactjs

I'm learning react by developing a simple app.
I've implemented a protecred route after login. Once loggedIn I'm redirected to the Hompage that present a navbar with two cascading dropdown and a sidebar with an accordion
Selecting the first dropdown the second is populated. Selecting the second dropdown the sidebar accordion is populated.
Everithing works but I have an issue I'm not able to solve
I have setted my route like this
const AppRoutes = () => {
return (
<>
<Switch>
<Route path = '/login' component={LogIn}/>
<PrivateRoute path = '/' component={PPC} exact={true}/>
</Switch>
</>
);
}
export default AppRoutes;
How can I manage further route within the private route? For axample I have a Component Main and a component Sidebar
Inside Sidebar I have Link to='/test'
import React from 'react';
import { Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
const Feature = (props) => {
const {data, selected, clicked} = props
return (
<Card.Body>
{data.map(i => {
return (
<Link to='/test'
// href="#"
onClick={() => selected(i.id)}
key={i.id}
className={clicked === i.id ? 'sidemenu-2-level active' : 'sidemenu-2-level'}
>
{i.feature}
</Link>
)
})}
</Card.Body>
)
}
export default Feature;
teh main is like this
import React from 'react';
import { Row, Col } from 'react-bootstrap'
import "./style.main.css";
import Test from './test';
import PrivateRoute from '../../../routes/privateRoute'
const Main = () => {
return (
<Row id='main'>
<Col className='mainCol'>
<PrivateRoute path = '/test' component={Test} />
</Col>
</Row>
);
}
export default Main;
Basically I want the test component tobe rendered inside Main component
When I click on the link I get a white page.
How to manage it?
EDIT: Finally I got it. The issue was the exact into the private route. Removing that option now everything works

Related

Clicking on a Card component should create a new route and display further information

I'm new to react-router v6
I have 4 components, App, CardList, Card and CardInfo. There is data (an array of objects, each object represents a movie) coming from an API that gets saved in App.js with useState hook.
Within CardList, I use map to iterate over the array to generate a bunch of Card components and passing in data via props.
What I want now is to be able to click on any Card component and for it to navigate to a different route, e.g. localhost:3000/1 (for Card with the id of 1), localhost:3000/2 (for Card with the id of 2) etc. and within each route that corresponds to the Card id, there would be a box/modal (CardInfo.js) component with further information about the movie.
I'm trying to accomplish this with react-router-dom (version 6).
It looks like within the CardList.js or Card.js component you would need to create links (<Link>) and routes (<Route>) (both which are equal to the number of movies in the data) on the fly with the .map function and wrapping the Card component in <Link> and <Route> tags. Something like
{items.map(movie => (
<Route path="/:id" element={<Card items={movies} />} exact>
<Link to={`/${movie.id}`}>
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
</Link>
</Route>
))}
Obviously that doesn't work.
App.js:
import './App.css';
import CardList from './features/Card/CardList';
import CardInfo from './features/Card/CardInfo';
import { useState, useEffect } from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
const getData = fetch('https://api.com/movies')
.then(data => data.json())
.then(items => { setMovies(items) })
}, [])
return (
<BrowserRouter>
<Routes>
<Route path="/:id" element={<CardInfo items={movies} />} exact></Route>
</Routes>
<CardList items={movies} />
</BrowserRouter>
);
}
export default App;
CardList.js:
import React from 'react'
import Card from './Card'
import "./CardList.css";
const CardList = ({ items }) => {
return (
<div className="cardList">
{items.map(movie => (
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
))}
</div>
)
}
export default CardList
Card.js:
import React from 'react'
import "./Card.css";
import { Link } from 'react-router-dom';
function Card(props) {
return (
<Link to={`/${props.id}`}>
<div className="card">
<div>
<img src={props.img} />
<h2>{props.name}</h2>
</div>
</div>
</Link>
)
}
export default Card
CardInfo.js:
import React from 'react'
function CardInfo(props) {
return (
<div>
<p>{props.description}</p>
</div>
)
}
export default CardInfo
your code structure is correct. You just need to useLink, you don't need en external Route component for every card you map.
Here is a link for further information. Hope you find it helpful.
https://stackoverflow.com/a/57059249/17715977

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

Next JS, React have child component tell parent what to render

I am new to NextJS and React and want to create some dynamic routing. I have a parent page that is calling a child component which show some navigation.
In my parent component I have a layout that includes a header and right sidebar which will show on each page, but depending on the link that is clicked in my Nav component the body content will change.
My parent component:
import React from 'react';
import { Header } from './Header;
import { SubNav } from './SubNav;
import { Sidebar } from './Sidebar;
export const AboutPage = () => {
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
In my child Nav component I am mapping through some data that includes a 'value' for each different section that I have defined in an array.
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
router.push(path, path, { shallow: true });
}}
/>
The page routes correctly with the value of the section changing each time I click on a different Navigation button but I can't get the content to change in the parent component, I have tried creating a function in the parent that will get the value from the child so that I can use it to define the sections in the parent, but it ends up creating an infinite loop. Confused on how to get the two to work together.
You can detect the pathname with the useRouter Hook.
Then you can check in a switch case statement which site the user is on and render the components for the route.
import { useRouter } from next/router
Inside the component
const router = useRouter();
The function to return the component
function renderSidebar(pathname){
switch(pathname){
...
}
Inside the render function
{ renderSidebar(router.pathname) }
See further usage in the NextJS docs
https://nextjs.org/docs/api-reference/next/router
import React from 'react';
import {useState} from 'react';
import { Header } from './Header';
import { SubNav } from './SubNav';
import { Sidebar } from './Sidebar';
export const AboutPage = () => {
const [section,setSection] = useState()
sectionHandler = (section) => {
setSection(section)
}
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
and in your child comp
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
props.sectionHandler(section)
router.push(path, path, { shallow: true });
}}
/>
don't forget to pass sectionhandler from parent to child comp

How to create a condition depending on the route one is in?

I have an app that is divided in 3 sections, a Header, Middle and Popular
<>
<Header />
<Middle />
<Popular />
</>
Header has a button that says 'Book it now' <Link to="/booking"> <button> Book it now </button> </Link>. When clicked, it uses React Router to switch route in the Middle component.
The 'Middle' component:
<Switch>
<Route path="/booking"> //When 'Book it now' is clicked, this is displayed.
<Booking />
</Route>
<Route path="/">
<Form />
</Route>
</Switch>
I would like to now make that Header button disappear when the route is '/booking'. I could create state and switch it to false after the button is clicked to not display the button, but I was thinking it would be pretty handy if I could do something like this in the Header file:
if (route === '/booking') {
//don't display button in Header (also, how would I do this?)
}
You can use withRouter imported from react-router-dom
import { withRouter } from 'react-router-dom';
const OtherComponent = withRouter(props => <MyComponent {...props}/>);
class MyComponent extends Component {
render() {
const { pathname } = this.props.location;
if(pathnanme != 'booking') {
<button>Hidden on booking</button>
}
}
}
That should be your Booking component:
import { withRouter } from 'react-router-dom';
const Booking = (props) => {
const { pathname } = props.location;
console.log(pathname);
return (
<div>
{(pathname !== 'booking' && pathname !== '/booking') ? <button>Hidden on booking</button> : null}
<span>always shown</span>
</div>
);
}
export default withRouter(Booking)
Hope this helps you
If you are using react-router v5 then I like to use hooks:
let location = useLocation();
let isBookingPage = location.pathname.includes('booking')
<>
{!isBookingPage && <Header />}
<Middle />
<Popular />
</>
By using the React Router 'useLocation' hook, you can have access to the route at which the user is currently at:
import { useLocation } from 'react-router-dom'
const location = useLocation() //with no arguments
console.log(location.pathname) //e.g. '/' or '/booking'
The location can the be used to conditionally render anything, in any component, depending on the route the user is at
location.pathname === '/booking' && <button> Book it now </button>

Props don't seem to be passed along in a React app

I recently started working with React so forgive me if my question is very basic. Props in a component don't seem to be passed along.
Below is my code.
dogDetails component
import React from 'react';
const DogDetails = (props) => {
return (
<div>
<h4>{'Dog details of '+ props.breed}</h4>
</div>
)
};
export default DogDetails;
In Dog component I have a method that returns a DogDetails component as shown below.
import React , {Component} from 'react'
import Dog from './Dog/Dog';
import classes from './Dogs.css';
import Aux from '../../hoc/Auxillary/Auxillary';
import {Route} from 'react-router-dom';
import DogDetails from './Dog/DogDetails/DogDetails';
class Dogs extends Component {
state = {
loadedDogs: []
};
componentDidMount () {
this.setState({
loadedDogs:[]
})
}
dogDetailsHandler = (dog) =>{
console.log(dog.breed);
return <DogDetails breed={dog.breed}/>;
};
render() {
const loadDogs = this.state.loadedDogs.map(dog => {
return <Dog
url={dog.images[0].image1}
alt={dog.id}
breed={dog.breed}
temperament={dog.temperament}
id={dog.id}
key={dog.id}
clicked ={() => this.dogDetailsHandler(dog)}>
</Dog>
});
return (
<Aux>
{loadDogs}
</Aux>
)
}
}
export default Dogs;
I have omitted the content of the loadedDogs array to reduce the code size.
Below is the Dog component
import React from 'react';
import classes from './Dog.css';
import {Link, Route} from 'react-router-dom';
const dog = (props) => {
return(
<div className={classes.Dog}>
<div>
<img src={props.url} alt ={props.id}/>
</div>
<div>
<h4>{'Breed: ' + props.breed}</h4>
<h5>{'Temperament: ' + props.temperament}</h5>
<p>
<Link to = '#'>... Read more ...</Link>
</p>
<Link to={'/shop/'+ props.id}>
<button onClick={props.clicked}>Order</button>
</Link>
</div>
</div>
)
};
export default dog;
I'm routing the DogDetails in the MainContent component like this.
import React from 'react';
import classes from './MainContent.css';
import Dogs from './Dogs/Dogs';
import Contact from '../Contact/Contact';
import {Route} from 'react-router-dom';
import DogDetails from './Dogs/Dog/DogDetails/DogDetails';
const main = () =>{
return (
<div className={classes.MainContent}>
<Route path='/' exact component = {Dogs}/>
<Route path='/shop' exact component = {Dogs}/>
<Route path={'/shop/:id'} exact component={DogDetails}/>
<Route path='/contact' exact component ={Contact}/>
</div>
)
};
export default main;
Here is a sample code sandbox to demonstrate what I'm trying to work on. I want the DogDetails component to show up when the Order button is clicked.
Code Sandbox
The dogDetails component <h4> tag is returning undefined. Please help me find where I'm doing it wrong.
Capitalize both dogDetails and dogDetailsHandler
User-Defined Components Must Be Capitalized
from react docs
Codesandbox example
Since you are using routing, I'm not sure why you have a button handler inside of a routed <Link />. Clicking on this element will route you to /shop/:id, and the return method of dogDetailsHandler will do nothing.
I have emulated your code and added a <Route /> I'm not sure if this is what you are after, but when I click "Order", I'll get routed to /shop/:id with the DogDetails component being rendered as it should.
Add this routing component after your <Link /> component and see if this is the behavior you are after.
<Route path="/shop/:id" render={
() => <DogDetails {...props} />
} />

Resources