How to add React routing to mapped list items? - javascript

I'm studying React and trying to make a Todo app. I made a first section and a menu which should switch the app between two sections. I'm mapping buttons in order to add them class because they should have different styling after they are clicked. Now I need to add routing to these buttons but I can't make it work.
This is my code:
import React, { useState } from "react";
import Todo from "../Todo/Todo";
import About from "../About/About";
import { BrowserRouter as Router, Route, Link, Routes } from "react-router-dom";
import classnames from "classnames";
import styles from "./App.module.css";
const App = () => {
const [clicked, setClicked] = useState("");
const buttons = ["About me", 'Todos'];
return (
<Router>
<div>
<nav className={styles.menu}>
<ul className={styles.nav}>
{buttons.map((item, index) => {
return (
<li
key={index}
onClick={() => {
if (clicked === index) {
setClicked();
} else {
setClicked(index);
}
}}
>
<Link to={`/buttons/${index}`}>
{
<button
className={classnames({ [styles.button]: true, [styles.selected]: clicked === index
})}
>
{item}
</button>
}
</Link>
</li>
);
})}
</ul>
</nav>
<Routes>
<Route path="/" exact element={<About />} />
<Route path="/todo" element={<Todo />} />
</Routes>
</div>
</Router>
);`
export default App;
The error message I get in console is 'No routes matched location "/buttons/0"'. And the same for "/buttons/1".
Probably I indicated paths wrong somehow but I don't know how to fix it.

Related

react router giving sub path for clicking again on link --v6

here I am building a website and I am using react-router-dom
everything seems to work fine when I use the Navbar and Footer components in every page.
So I come up with idea to wrap the component in a wrapper which contains Navbar and Footer.
When I click on Link or maybe NavLink, it seems to work fine but when I click again on same or another link in navbar then it navigates to a sub path under the previous selected path.
like this
on Single Click:
http://localhost:3000/projects
on clicking the same link again:
http://localhost:3000/projects/projects
App.js
import './App.css';
import {BrowserRouter as Router, Route, Outlet, Routes} from 'react-router-dom'
import {CommonScreen, Wrapper, About, Email, Projects} from './Barell'
function App() {
return (
<>
<Router>
<Routes>
<Route index element={<Wrapper children={<CommonScreen/>}/>}/>
<Route path='about' element={<Wrapper children={<About/>}/>}/>
<Route path='projects' element={<Wrapper children={<Projects/>}/>}/>
<Route path='email' element={<Email/>}/>
</Routes>
</Router>
<Outlet/>
</>
);
}
export default App;
Navbar.jsx:
import React from 'react'
import '../../index.css'
import { NavLink } from 'react-router-dom'
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"projects",
},
{
name:"About",
slug:"about",
},
{
name:"Contact",
slug:"email",
},
]
export const Navbar = () => {
let activeStyle = {
textDecoration: "underline",
};
return (
<>
<nav>
<div id='brand'>Prema<span>culture</span></div>
<ul id='links'>
{
links.map((current,index) => (
<li>
<NavLink
key={index}
to={current.slug}
style={({ isActive }) =>
isActive ? activeStyle : undefined
}
>
{current.name}
</NavLink>
</li>
))
}
</ul>
</nav>
</>
)
}
Wrapper.jsx:
import React from 'react'
import { Navbar, Footer } from '../Barell'
export const Wrapper = ({children}) => {
return (
<>
<Navbar/>
{children}
<Footer/>
</>
)
}
You are using relative paths (versus absolute), so when already on a route, say "/projects", and then click a link that navigates to "about", the code links relative from "/projects" and results in "/projects/about".
To resolve you can make all the link paths absolute by prefixing a leading "/" character. Absolute paths begin with "/".
Example:
const links = [
{
name:"Home",
slug:"/",
},
{
name:"Projects",
slug:"/projects",
},
{
name:"About",
slug:"/about",
},
{
name:"Contact",
slug:"/email",
},
];
Additionally, to make the code more DRY you might also want to convert the Wrapper component into a layout route. Layout routes render an Outlet component for nested routes to be rendered into instead of a children prop for a single routes content.
Example
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Navbar, Footer } from '../Barell';
export const Layout = () => {
return (
<>
<Navbar/>
<Outlet />
<Footer/>
</>
)
}
...
function App() {
return (
<>
<Router>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<CommonScreen />} />
<Route path='about' element={<About />} />
<Route path='projects' element={<Projects />} />
</Route>
<Route path='email' element={<Email />} />
</Routes>
</Router>
<Outlet />
</>
);
}

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 />}>

unable to create triple nested routes in react router with material ui tabs

I'm trying to create triple nesting with react-router but it's not working.
Here is the expected output
I want to perform but I am trying to implement this with the router so my tab should get switch according to URL passed to it.
When the user just write / in the URL it should redirect to login page (working)
When the user write /dashboard it should redirect to the dashboard with side list (working)
When the user click on list items such as about in drawer of the dashboard then it should render about page.
Now my about page contains two tabs one for general and one for account (Everything was working until I implement this feature now the whole app has been crashed and not even showing the error)
Here is my code in sandbox. Note: I have commented on my third nested route because it was crashing entire app
App.js
import { Switch, Route } from "react-router-dom";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" exact component={Login} />
</Switch>
</div>
);
}
Dashboard.js for nested routing
import { Switch, Redirect, Route, useRouteMatch } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import AppDrawerBar from "../components/AppDrawerBar";
const Dashboard = () => {
const { path } = useRouteMatch();
return (
<>
<AppDrawerBar>
<Switch>
<Route path={`${path}/about`} component={About} />
<Route path={`${path}/home`} component={Home} />
<Redirect to={`${path}/home`} />
</Switch>
</AppDrawerBar>
</>
);
};
export default Dashboard;
About.js third nested routing to handle tab navigation after clicking on about list item
import { Switch } from "#material-ui/core";
import React from "react";
import { Redirect, Route } from "react-router-dom";
import TabMenu from "../components/TabMenu";
const About = (props) => {
return (
<>
<h1>Welcome to about</h1>
{/* This doesn't work.. this code is to render tab navigation */}
{/* <Switch>
<Redirect exact from={`${props.path}/about`} to="about/general" />
<Route
exact
path="/about/:page?"
render={(props) => <TabMenu {...props} />}
/>
</Switch> */}
</>
);
};
export default About;
TabMenu.js to handle my tabs
import { Paper, Tab, Tabs } from "#material-ui/core";
import React, { useState } from "react";
import General from "../pages/General";
import Account from "../pages/Account";
const TabMenu = (props) => {
const { match, history } = props;
const { params } = match;
const { page } = params;
const tabNameToIndex = {
0: "about",
1: "contact"
};
const indexToTabName = {
about: 0,
contact: 1
};
const [selectedTab, setSelectedTab] = useState(indexToTabName[page]);
const handleChange = (event, newValue) => {
history.push(`/about/${tabNameToIndex[newValue]}`);
setSelectedTab(newValue);
};
return (
<>
<Paper elevation={2}>
<Tabs
value={selectedTab}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
>
<Tab label="General" />
<Tab label="Profile" />
</Tabs>
</Paper>
{selectedTab === 0 && <General />}
{selectedTab === 1 && <Account />}
</>
);
};
export default TabMenu;

How to setup Routes using React Router in my ReactJS app?

I have some problem with react-router. It doesn't go to Edit page when I click on id. The id is in URL, but it doesn't do anything.
const Main = (props) => {
const { pathname } = props.location;
return (
<Fragment>
<div>
<div className="container">
<Header />
{pathname === "/create" ? <Create /> : null}
{pathname === '/edit/:id' ? <Edit /> : null}
{pathname === "/" ? <Home /> : null}
</div>
</div>
</Fragment>
);
};
export default withRouter(Main);
app.js:
require('./components/Index');
import React from 'react'
import ReactDOM from "react-dom"
import Index from "./components/Index";
import { BrowserRouter as Router} from "react-router-dom";
import { ToastContainer } from 'react-toastify';
const App =() =>{
}
if (document.getElementById('app')) {
ReactDOM.render(<Router> <Index /> <ToastContainer /></Router>, document.getElementById('app'));
}
index.js:
import React from "react";
import { Route, Switch } from "react-router-dom";
import Main from "./CRUD/Main";
import Create from "./CRUD/Create";
import Edit from "./CRUD/Edit";
import Home from "./CRUD/Home";
const Index = (props) => {
return (
<Main>
<Switch>
<Route path="/Create" component={Create} />
<Route path='/edit/:id' component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</Main>
);
};
export default Index;
I think main.js have some problems with pathname.
You don't need to do conditional rendering like you are doing in Main when using react-router. Switch will automatically render the first child <Route> or <Redirect> that will match the location.
Hence, you need to remove Main from your router component i.e. Index so that it looks like as shown below:
const Index = (props) => {
return (
<>
<NavBar /> {/* NavBar is optional, I just added for example */}
<Switch>
<Route path="/create" component={Create} />
<Route path="/edit/:id" component={Edit} />
<Route exact path="/" component={Home} />
</Switch>
</>
);
}
NavBar: (Links; just for example)
function NavBar() {
return (
<ul>
<li>
<Link to="/create">Go to create</Link>
</li>
<li>
<Link to="/edit/123">Go to edit with id = 123</Link>
</li>
<li>
<Link to="/">Go to home</Link>
</li>
</ul>
);
}
Now, when you click on the above links, it will automatically take you to the related component (as declared in routes i.e. Index). (no manually condition checking)
And example, to retrieve the URL param i.e. id in Edit component using useParams hook:
function Edit() {
const { id } = useParams<{ id: string }>(); // Remove <{ id: string }> if not using TypeScript
return <h2>Edit, {id}</h2>;
}

How can I maintain my props/states in ReactJS?

I'm currently building a React application with the following workflow:
List of application categories, the user will select one
Once a user has selected an application category, they will select a water type from a list
A list of products will then be displayed depending on the category and type selected.
They can then select a product to see the information i.e. product charts, images etc.
The problem:
Once a user selects a product, if they click the back button, the category and type props are lost.
Solution required:
I need to be able to maintain these props/state at all times, allowing them to be updated if the user goes back and changes category/type
I've included my code for reference below.
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById("root")
);
App.js
import React from "react";
import { Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import WaterType from "./components/WaterType";
import Products from "./components/Products";
import Product from "./components/Product";
import "./App.css";
function App() {
return (
<div className="App">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/waterType" component={WaterType} />
<Route path="/products/:productName" component={Product} />
<Route path="/products" component={Products} />
</Switch>
</div>
);
}
export default App;
Home.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import CategoryData from "./data/CategoryData";
class Home extends Component {
render() {
return (
<>
<h1>Categories</h1>
<ul>
{CategoryData.map((cat, i) => (
<li key={i}>
<Link
to={{
pathname: "/waterType",
name: cat.name,
}}
>
<img src={cat.imageURL} alt={cat.name} />
{cat.name}
</Link>
</li>
))}
</ul>
</>
);
}
}
export default Home;
WaterType.js
import React from "react";
import { Link } from "react-router-dom";
import WaterTypeData from "./data/WaterTypeData";
const WaterType = ({ location }) => {
const categorySelected = location.name;
return (
<>
<h1>Water Types</h1>
<p>Current category: {categorySelected}</p>
<ul>
{WaterTypeData.map((type, i) => (
<li key={i}>
<Link
to={{
pathname: "/products",
categorySelected: categorySelected,
waterType: type.name,
}}
>
{type.name} - {type.description}
</Link>
</li>
))}
</ul>
</>
);
};
export default WaterType;
Products.js
import React from "react";
import { Link } from "react-router-dom";
import ProductData from "./data/ProductData";
const Products = ({ location }) => {
const categorySelected = location.categorySelected;
const waterType = location.waterType;
const ProductsResult = ProductData.filter(x => x.categories.includes(categorySelected) && x.waterTypes.includes(waterType));
return (
<>
<h1>Products</h1>
<p>Current category: {categorySelected && categorySelected}</p>
<p>Water Type: {waterType && waterType}</p>
<div className="products">
<ul>
{ProductsResult.map((item, i) => (
<li key={i}>
<Link
to={{
pathname: '/products/' + item.slug,
name: item.name,
}}
>
{item.name}
</Link>
</li>
))}
</ul>
</div>
</>
);
};
export default Products;
Product.js
import React from "react";
const Product = ({ location }) => {
const productName = location.name;
return (
<>
<h1>{productName}</h1>
</>
);
};
export default Product;
The easiest solution that I can think of is to keep your selected choices (category and water type) in a top level context.
Something like this:
// ChoicesProvider.js
import React, { createContext, useState } from "react";
export const ChoicesContext = createContext(null);
export const ChoicesProvider = ({ children }) => {
const [choices, setChoices] = useState({
category: null,
waterType: null,
});
return (
<ChoicesContext.Provider value={{ choices, setChoices }}>
{children}
</ChoicesContext.Provider>
);
};
…and then in your entry point:
// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import { ChoicesProvider } from "./context/ChoicesProvider";
ReactDOM.render(
<React.StrictMode>
<ChoicesProvider>
<Router>
<App />
</Router>
</ChoicesProvider>
</React.StrictMode>,
document.getElementById("root")
);
…and then each time you pick a category / waterType save the selected state in a context using setChoices defined in context. For example:
// Home.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import CategoryData from "./data/CategoryData";
import { ChoicesContext } from "../../context/ChoicesContext";
const Home = () => {
const { choices, setChoices } = useContext(ChoicesContext);
return (
<>
<h1>Categories</h1>
<ul>
{CategoryData.map((cat, i) => (
<li key={i}>
<Link
onClick={() => setChoices({ ...choices, category: cat.name })}
to={{
pathname: "/waterType",
name: cat.name,
}}
>
<img src={cat.imageURL} alt={cat.name} />
{cat.name}
</Link>
</li>
))}
</ul>
</>
);
};
export default Home;
Hopefully that gives you an idea.
Have a great day 👋
First of all, I am not sure if your Router setup is necessary. React is great for single page applications (SPA), this means, you don't neet new page for every single functionality. You can very comfortably build your application on single Route, changing just the components you currently need.
So the first possible solution is to build the application on single page, using three simple states
[category, setCategory] = useState(null)
[waterType, setWatertype] = useState(null)
[product, setProduct] = useState(null)
Based on this you can simply show or hide the select options
<div id="selections">
{!category && (<Categories />)}
{(category && !watertype) && (<WaterTypes />)}
{category && watertype) && (<Products />)}
</div>
category && <WaterTypes /> means, WaterTypes will be displayed only if category is true.
This if course requires your Link to be replaced with something like
<button type="button" onClick={() => setCategory(cat.name)}>{cat.name}</button>
With this approach you can then handle back and forward button to manipulate your states and URL, so the user has access to desired category with hyperlink like www.youreshop.com?category=first&watertype=second
Another approach is to add context to your app, what would allow you to share states between individual components.
I highly recommend Redux (https://redux.js.org/) for application state managemt.
In general it's a good idea to include Redux to modern React applications, and in your case it seems to be exactly what you're looking for.
Hope this helps.
Cheers

Categories

Resources