Using a simple link to / route to via React browser Router.
I got it to work fine with routing to the root component (/), however it does not function as expected when trying to route to (/drink/:drinkId), though the URL changes and the page loads if I manually try to access it.
App component:
import React from "react";
import "./App.css";
import Cocktails from "./Cocktails";
import { Container } from "react-bootstrap";
import NavApp from "./Navbar";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import DrinkDetails from "./DrinkDetails";
import "./App.css";
function App() {
return (
<Router>
<Container className="App-container">
<NavApp />
<Switch>
<Route exact path="/">
<Cocktails size={20} />
</Route>
<Route exact path="/drink/:drinkId">
<DrinkDetails />
</Route>
</Switch>
</Container>
</Router>
);
}
export default App;
Drink details component:
import { React, useState, useEffect } from "react";
import { Jumbotron, Button } from "react-bootstrap";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
} from "react-router-dom";
import axios from "axios";
function DrinkDetails() {
let { drinkId } = useParams();
const [drink, setDrink] = useState(null);
const [ingrdts, setIngrdts] = useState([]);
useEffect(() => {
const getDrinkSpecs = async () => {
const res = await axios.get(
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${drinkId}`
);
let newDrink = res.data.drinks[0];
setDrink(newDrink);
let newIngrdts = [];
for (let i = 1; i <= 15; i++) {
if (newDrink[`strIngredient${i}`] != null) {
let ingrdtVal = {};
ingrdtVal["ing"] = newDrink[`strIngredient${i}`];
ingrdtVal["val"] = newDrink[`strMeasure${i}`];
newIngrdts.push(ingrdtVal);
}
}
setIngrdts([...newIngrdts]);
};
getDrinkSpecs();
}, [drinkId]);
return drink ? (
<Jumbotron>
<h1>
{drink.strDrink}
<img src={drink.strDrinkThumb} />
</h1>
<p>Glass: {drink.strGlass}</p>
<p>Category: {drink.strCategory}</p>
<p>Instructions: {drink.strInstructions}</p>
{ingrdts.map((ingrdt) => (
<p>
{ingrdt.ing} : {ingrdt.val}
</p>
))}
<p>
<Button variant="primary">Learn more</Button>
</p>
</Jumbotron>
) : (
<Jumbotron>
<h1>Hmmm... we don't have this yet!</h1>
<p>
This is a simple hero unit, a simple jumbotron-style component for
calling extra attention to featured content or information.
</p>
<p>
<Button variant="primary">Learn more</Button>
</p>
</Jumbotron>
);
}
export default DrinkDetails;
and this is where I use Link:
import React from "react";
import { Button, Card } from "react-bootstrap";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import "./Card.css";
function CardDrink({ data, select }) {
return (
<Router>
<Card className="Cocktail-card">
<Link to={`/drink/${data.idDrink}`}>
<Card.Img
variant="top"
src={data.strDrinkThumb}
className="Cocktail-card-img"
onClick={() => select(data.idDrink)}
/>
</Link>
<Card.Body>
<Card.Title>{data.strDrink}</Card.Title>
</Card.Body>
</Card>
</Router>
);
}
export default CardDrink;
Remove <Router> from CardDrink component. You need only one Router at root level which you already have in App component.
Also, as a practice don't keep unused imports in your component. I see Router imported in DrinkDetails component as well.
From docs
To use a router, just make sure it is rendered at the root of your element hierarchy. Typically you’ll wrap your top-level element in a router.
Try to use the useHistory() hook:
import React from "react";
import { Button, Card } from "react-bootstrap";
import { BrowserRouter as Router, Switch, Route, Link, useHistory } from "react-router-dom";
import "./Card.css";
function CardDrink({ data, select }) {
const history = useHistory()
return (
<Router>
<Card className="Cocktail-card">
<div onClick={()=>history.push(`/drink/${data.idDrink}`)}>
<Card.Img
variant="top"
src={data.strDrinkThumb}
className="Cocktail-card-img"
onClick={() => select(data.idDrink)}
/>
</div>
<Card.Body>
<Card.Title>{data.strDrink}</Card.Title>
</Card.Body>
</Card>
</Router>
);
}
export default CardDrink;
Related
Is there a way to stop the route from unmounting? In my app I would need to keep react state between switching the routes.
Here you can see that, if we change route then home respective route component will mount and unmount. I don't want.
CODESANDBOX DEMO
I've looked in
How can I prevent the unmount in React Components?
How can I prevent React from unmounting/remounting a component?
but doesn't work for me.
index.js
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./App";
import First from "./routes/First";
import Second from "./routes/Second";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<BrowserRouter>
<Routes>
<Route path="" element={<App />} />
<Route path="first" element={<First />} />
<Route path="second" element={<Second />} />
</Routes>
</BrowserRouter>
);
App.js
import * as React from "react";
import { Link } from "react-router-dom";
export default function App() {
return (
<div>
<h1>Prevent Routing</h1>
<nav
style={{
borderBottom: "solid 1px",
paddingBottom: "1rem"
}}
>
<Link to="/first">First</Link> |<Link to="/second">Second</Link>
</nav>
</div>
);
}
First.js
import * as React from "react";
import { useEffect } from "react";
import { Link } from "react-router-dom";
export default function First() {
useEffect(() => {
console.log("mounted First");
return () => console.log("unmounted First");
}, []);
return (
<main style={{ padding: "1rem 0" }}>
<Link to="/">Home</Link>
<h2>First</h2>
</main>
);
}
Second.js
import * as React from "react";
import { useEffect } from "react";
import { Link } from "react-router-dom";
export default function Second() {
useEffect(() => {
console.log("mounted Second");
return () => console.log("unmounted Second");
}, []);
return (
<main style={{ padding: "1rem 0" }}>
<Link to="/">Home</Link>
<h2>Second</h2>
</main>
);
}
React Router will always unmount the component when it's not matched by a path.
You could fix this by combining both Components with a double route, each component can determine what he will render based on the current path.
So the main routing will look something like:
<Route path={[ '/first', '/second' ]}>
<First {...{}} />
<Second {...{}} />
</Route>
Then use useLocation in the component to toggle the render:
import { useLocation } from "react-router-dom";
function First(props) {
const location = useLocation();
if (location.pathname !== '/first') {
return null
}
}
I am trying to implement a cart as a Modal on my project. The issue is that the Backdrop is rendering as it should but the Overlay isn't. It has been 30 minutes but I can't figure out the error, attaching the files below.
For now, the Modal should render all the time according to the code as I have yet to implement the conditional rendering but that isn't an issue, the issue is that the Overlay isn't showing up for some reason.
App.js file
import "./App.css";
import { useState } from "react";
import Middlebar from "./Components/Middlebar";
import FilterTab from "./Components/FilterTab";
import ProductPage from "./Components/ProductPage";
import Navbar from "./Components/Navbar";
import { Route, Switch } from "react-router-dom";
import Data from "./Components/data.json";
import ProductDetail from "./pages/ProductDetail";
import Cart from "./Components/Cart";
function App() {
const [searchedTerm, setSearchedTerm] = useState("");
const getSearchTerm = (term) => {
setSearchedTerm(term);
};
return (
<Switch>
<Route path="/" exact>
<Navbar onSearch={getSearchTerm} />,
<Cart/>,
<Middlebar />,
<div className="filter-product row">
{" "}
<FilterTab></FilterTab>{" "}
<ProductPage searchTerm={searchedTerm}></ProductPage>{" "}
</div>
</Route>
<Route path="/product/:id">
<ProductDetail></ProductDetail>
</Route>
</Switch>
);
}
export default App;
Modal.js file
import { Fragment } from "react";
import ReactDOM from "react-dom";
import "./Modal.css";
const Backdrop = (props) => {
return <div onClick = {props.onClick} className="backdrop"></div>;
};
const ModalOverlay = (props) => {
return (
<div className="modal">
<div className="content">{props.children}</div>
</div>
);
};
const portalElement = document.getElementById("overlays");
export default function Modal(props) {
return (
<Fragment>
{ReactDOM.createPortal(<Backdrop />, portalElement)}
{ReactDOM.createPortal(
<ModalOverlay>{props.children}</ModalOverlay>,
portalElement
)}
</Fragment>
);
}
Cart.js file
import React from "react";
import Modal from "../Modal";
import "./Cart.css";
export default function Cart() {
return (
<Modal>
<div className="total">
<span>Total</span>
<span>hi</span>
</div>
<div className="actions">
<button className="button--alt">
Close
</button>
<button className="button">Order</button>
</div>
</Modal>
);
}
i've been following an online course about react but it's a bit dated and i've been struggling through all the lessons to understand the concepts explained while applying the new syntax instead of the the one shown in the videos. i've always more or less managed but now i'm stuck because, after adding the react router dom web app, my pages don't render anymore. no errors nor warnings are shown and no matter how much i look, i can't seem to find the mistake. my main right now looks like this:
import React from 'react';
import Header from './header';
import MyPlacesList from './myplaceslist.js';
import Footer from './footer';
import CreatePlace from './createPlace.js';
import * as attAPI from '../utils/attractionsAPI';
import { Router, Route } from 'react-router-dom';
class Main extends React.Component{
state = {
attractions: [],
}
componentDidMount(){
attAPI.getAll().then((attractions) => {
this.setState({attractions})
})
}
removePlace = (attraction) => {
this.setState((state) => ({
attractions: state.attractions.filter((attr) => attr.id !== attraction.id)
}))
attAPI.remove(attraction);
}
render(){
return (
<Router>
<Fragment>
<Header titolo = 'My Places' sottotitolo = 'I miei posti preferiti'/>
<Route exact path = '/' render = {() => (
<MyPlacesList attractions = {this.state.attractions}
onRemovePlace = {this.removePlace} />
)}/>
<Route path = '/create' render = {() => (
<CreatePlace />
)}/>
<Footer />
</Fragment>
</Router>
)
}
}
export default Main;
and this is the header:
import React from 'react';
import AppBar from '#mui/material/AppBar';
import Box from '#mui/material/Box';
import Toolbar from '#mui/material/Toolbar';
import Typography from '#mui/material/Typography';
import IconButton from '#mui/material/IconButton';
import Icon from '#mui/material/Icon';
import AddCircleIcon from '#mui/icons-material/AddCircle';
import { Link } from 'react-router-dom';
class Header extends React.Component{
render(){
// return <nav>
// <div className = "nav-wrapper">
// {this.props.titolo}
// </div>
// </nav>
return <Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<Typography
variant="h6"
noWrap
component="div"
sx={{ flexGrow: 1, display: { xs: 'none', sm: 'block' } }}
>
{this.props.titolo}
</Typography>
<Link to = '/create'>
<IconButton>
<AddCircleIcon />
</IconButton>
</Link>
</Toolbar>
</AppBar>
</Box>
}
}
export default Header;
before adding react router dom i was using the state to call the components MyPlacesList/CreatePlace (createPlace only gets shown when i click on a button) and it worked just fine, the problem appeared after i tried to use <Route>. any ideas in how could i fix this? thank you in advance!
EDIT - here's my index page too:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Main from './components/main';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<Main/>
</BrowserRouter>, document.getElementById('root'));
Things to correct
Use React.Fragment instead of just Fragment or import from react.
Add All Routes between Routes component.
You have already add Router or BrowserRouter in index.js so don't need to add again.
There is a change in nesting in version 5.
So ultimately the nesting should be in version 6
<BrowserRouter>
<Routes>
<Route>
<Route>
</Routes>
</BrowserRouter>
import MyPlacesList from "./myPlacesList.js";
import CreatePlace from "./createPlace.js";
import { Route, Routes } from "react-router-dom";
class App extends React.Component {
render() {
return (
<React.Fragment>
<div>Some Header component</div>
<Routes>
<Route
exact
path="/"
render={() => (
<MyPlacesList
attractions={this.state.attractions}
onRemovePlace={this.removePlace}
/>
)}
/>
<Route path="/create" render={() => <CreatePlace />} />
</Routes>
</React.Fragment>
);
}
}
export default App;
React Components not showing using Router, Route, Link
By clicking the Menu button I can tell from the URL: "http://localhost:3000/Menu", that I have linked to the right route. However, the Menu component just won't show up.
Here is the code for my NavBar Component:
import React, {Component} from 'react';
import './index.css'
import logo from '../../image/laoma-logo.png'
import {NavItems} from "./NavItems/NavItems";
import {Link} from "react-router-dom";
class HeaderNav extends Component {
state = {clicked: false}
handleClick = () =>{
this.setState({clicked: !this.state.clicked})
}
render() {
return (
<nav className="NavItems">
<div>
<img className="NavLogo" src={logo} alt='LaoMa Logo'/>
</div>
<div className="menu-icon" onClick={this.handleClick}>
<i className={this.state.clicked ? 'fas fa-times':'fas fa-bars'}>
</i>
</div>
<ul className={this.state.clicked? 'nav-menu active': 'nav-menu'}>
{NavItems.map((item, index)=> {
return (
<li key={index}>
<Link className={item.cName} href={item.url} to={item.title}>
{item.title}
</Link>
</li>
)
})}
</ul>
</nav>
);
}
}
export default HeaderNav;
Here is my Menu component,
import React, {useState} from 'react';
import items from "../../../data/items";
import Categories from "./Categories";
import Items from "./Items";
import './index.css'
const allCategories = ['all', ...new Set(items.map((item) => item.category))];
function Menu() {
const [menuItems, setMenuItems] = useState(items);
const [categories] = useState(allCategories);
const filterItems = (category) => {
if (category === 'all') {
setMenuItems(items);
return;
}
const newItems = items.filter((item) => item.category === category);
setMenuItems(newItems);
};
return (
<main>
{/* <Route path={NavItems.title} component={Menu}/> */}
<section className='menu section'>
<Categories categories={categories} filterItems={filterItems}/>
<Items items={menuItems}/>
</section>
</main>
);
}
export default Menu;
Here is my App components:
import HeaderNav from './components/HeaderNav/index';
import {Component} from "react";
import { BrowerRouter as Router, Route} from 'react-router-dom';
import './App.css';
import Menu from './components/Body/Menu';
export default class App extends Component {
render() {
return (
<div className="App">
<Router>
<HeaderNav/>
<Route path='/Menu'>
<Menu/>
</Route>
</Router>
</div>
);
}
}
Just from the sake of completion, here is my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
I realized that I should use exact path='' instead of path='' because how router works is that it will go through every routes and apply the very first one in the list. And with partial matching, it will return the component or page pf the first nested route, and here 'exact' comes into play and solve the issue.
Hi i am trying to change the route of my website to go to Contact, when the menu is clicked.
Whenever i press the button for the homepage which is "Forside", the routing works perfectly.
But as soon as i press the button for the contact which is "Kontakt", the url changes and no component renders. But if i refresh the page, it shows up..
Any ideas what is wrong with my code, or any way to fix it?
All help will be appreciated thanks.
My App.js
import React from 'react';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import Forside from './forside';
import Kontakt from './Kontakt';
import Header from './Header'
const App = () => {
return(
<Router>
<div>
<Header/>
<Switch>
<Route exact path="/" component={Forside}/>
<Route path="/kontakt" component={Kontakt}/>
</Switch>
</div>
</Router>
)
}
export default App
And this is where i link to the different routes.
import React, { useState } from 'react';
import './header.css'
import { makeStyles } from '#material-ui/core/styles';
import BottomNavigation from '#material-ui/core/BottomNavigation';
import BottomNavigationAction from '#material-ui/core/BottomNavigationAction';
import ContactMailIcon from '#material-ui/icons/ContactMail';
import LocationOnIcon from '#material-ui/icons/LocationOn';
import {Link} from 'react-router-dom';
const useStyles = makeStyles({
root: {
width: 500,
},
});
const Header = () => {
const classes = useStyles();
const [value, setValue] = React.useState(0);
return(
<div className="hed">
<h1 className="logo"><span>IT</span> ARBEJDE.DK</h1>
<BottomNavigation
value={value}
className="nav"
onChange={(event, newValue) => {
setValue(newValue);
}}
showLabels
className={classes.root}
>
<Link to="/">
<BottomNavigationAction label="Søg job" icon={<LocationOnIcon />} />
</Link>
<Link to="/kontakt">
<BottomNavigationAction label="Kontakt" icon={<ContactMailIcon/>} />
</Link>
</BottomNavigation>
</div>
)
}
export default Header
add exact to it to your route
<Route exact path="/kontakt" component={Kontakt}/>
<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>
Since i didn't get a answer, i have figured it out on my own. So i would just like to share the solution, for other people who maybe are expecting the same bug.
I fixed the problem, by restructuring my code.
I placed my router inside the index.js component like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {BrowserRouter as Router} from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Router>
<App/>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
Then placed the Header code inside my App.js, instead of having the Component rendering.
So i refactored the code like this:
import React from 'react';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import Kontakt from './Kontakt';
import Forside from './forside';
import { makeStyles } from '#material-ui/core/styles';
import BottomNavigation from '#material-ui/core/BottomNavigation';
import BottomNavigationAction from '#material-ui/core/BottomNavigationAction';
import ContactMailIcon from '#material-ui/icons/ContactMail';
import LocationOnIcon from '#material-ui/icons/LocationOn';
import {Link} from 'react-router-dom';
import './header.css'
const useStyles = makeStyles({
root: {
width: 500,
},
});
const App = () => {
const classes = useStyles();
const [value, setValue] = React.useState(0);
return(
<div>
<div className="header-container">
<h1 className="logo"><span>IT</span> ARBEJDE.DK</h1>
<BottomNavigation
value={value}
className="nav"
onChange={(event, newValue) => {
setValue(newValue);
}}
showLabels
className={classes.root}
>
<BottomNavigationAction label="Søg job" component={Link} to="/" icon={<LocationOnIcon />} />
<BottomNavigationAction label="Kontakt" component={Link} to="/kontakt" icon={<ContactMailIcon/>} />
</BottomNavigation>
</div>
<Switch>
<Route exact path="/" component={Forside}/>
<Route exact path="/kontakt" component={Kontakt}/>
</Switch>
</div>
)
}
export default App