I have created react app. I have directory components with folder pages where I have FindAllMetadata component in which I am making GET request and getting all metadata from API. I am passing loadedMetadata to AllMetadataTable as props. AllMetadataTable component is located in other folder called data. There I am displaying some information about fetched metadata items in table ( Creator, Time Created, Format ) and other fetched properties I am not displaying. In a table aside of the every fetched metadata I have + button which when clicked makes link ( route ) to the new page where I want display all information about fetched metadata, single clicked metadata item. Details component is located in folder pages. I want to display clicked Metadata from AllMetadataTable in Details page or in Metadata component.
Here is my App.js :
import React, { Suspense } from 'react';
import { Switch, Route, BrowserRouter } from 'react-router-dom';
import classes from './App.module.css'
import LoadingSpinner from './components/UI/LoadingSpinner';
import Layout from './components/layout/Layout';
import Footer from './components/layout/Footer';
import RequestMenu from './components/UI/RequestMenu';
// load components only when user gets to them
const Dashboard = React.lazy(() => import('./components/pages/Dashboard'));
const NewData = React.lazy(() => import('./components/pages/NewData'));
const NotFound = React.lazy(() => import('./components/pages/NotFound'));
const FindAllReceivedRequests = React.lazy(() => import('./components/pages/FindAllReceivedRequests'));
const FindAllGivenConsents = React.lazy(() => import('./components/pages/FindAllGivenConsents'));
const ReadConsent = React.lazy(() => import('./components/pages/ReadConsent'));
const FindData = React.lazy(() => import('./components/pages/FindData'));
const FindAllMetadata = React.lazy(() => import ('./components/pages/FindAllMetadata'));
const NewPartnerRequest = React.lazy(() => import('./components/pages/NewPartnerRequest'));
const FindAllGivenRequests = React.lazy(() => import('./components/pages/FindAllGivenRequests'));
const FindAllReceivedConsents = React.lazy(() => import('./components/pages/FindAllReceivedConsents'));
const Metadata = React.lazy(() => import('./components/data/Metadata'));
function App() {
return (
<BrowserRouter>
<Layout>
<Suspense fallback= { <div className = { classes.centered }> <LoadingSpinner /> </div> } >
<Switch>
<Route path ='/' exact>
<Dashboard />
<FindData />
</Route>
<Route path= '/new-data' exact>
<NewData />
</Route>
<Route path= '/metadata' exact>
<FindAllMetadata />
</Route>
<Route path = '/data'>
<Metadata />
</Route>
<Route path= '/request' exact>
<RequestMenu />
<FindAllReceivedRequests />
<section style = {{ marginTop: '5rem',
}}>
<FindAllGivenConsents />
</section>
</Route>
<Route path= '/givenrequest' exact>
<RequestMenu />
<FindAllGivenRequests />
<section style = {{ marginTop: '5rem',
}}>
<FindAllReceivedConsents />
</section>
</Route>
<Route path= '/transfer-data' exact>
<ReadConsent />
</Route>
<Route path= '/partner-request' exact>
<NewPartnerRequest />
</Route>
<Route path= '*'>
<NotFound />
</Route>
</Switch>
</Suspense>
</Layout>
<Footer />
</BrowserRouter>
);
}
export default App;
Here is my FindAllMetadata.js where I am fetching allMetadata ( it is working ):
import React, { useState, useEffect, useMemo } from 'react';
import AllMetadataTable from '../data/AllMetadataTable';
import LoadingSpinner from '../UI/LoadingSpinner';
import styles from '../UI/Messages.module.css';
import styled from '../style/Form.module.css';
import { readAllMetadata } from '../lib/api';
const FindAllMetadata = () => {
const [allMetadata, setAllMetadata] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [enteredMetadataFilter, setEnteredMetadataFilter] = useState('all');
// When page is loaded immediately fetch (and display) all metadata (only running the effect when enteredMetadataFilter changes)
useEffect(() => {
// fetch metadata by entered request filter (all or my)
const readAllMetadataHandler = async () => {
setIsLoading(true);
setError(null);
try {
const loadedAllMetadata = await readAllMetadata(enteredMetadataFilter);
setAllMetadata(loadedAllMetadata);
} catch (error) {
setError(error);
}
setIsLoading(false);
};
readAllMetadataHandler();
}, [enteredMetadataFilter]);
// display fetched content
const content = useMemo(() => {
if (error) {
return <div className={styles.negative}> { error.message } </div>;
} else if (isLoading) {
return <section style = {{margin : '1rem 17rem' }} ><LoadingSpinner /> </section>;
} else {
return (
<AllMetadataTable allMetadata = { allMetadata }
metadataFilter = { enteredMetadataFilter }
issLoading = { isLoading }
/>
);
}
}, [isLoading, error, allMetadata, enteredMetadataFilter]);
return (
<>
{/** pick filter for displaying metadata */}
{!isLoading &&
<section style= {{ marginLeft : '-10rem'}} >
<select className={styled.selectControl}
onChange={ event => {
setEnteredMetadataFilter(event.target.value);
}} >
<option value='' disabled style={{ color: '#cccccc' }} > Choose an option
</option>
<option value = 'all'> All Metadata </option>
<option value = 'my'> My Data </option>
</select>
</section>
}
<section>
{/**display content by status: error, loading, allmetadata, mymetadata */}
{ content }
</section>
</>
)
}
export default FindAllMetadata;
Here is my AllMetadataTable.js component where I am displaying fetched metadata in a table ( it is working and when I click + button it is redirecting me to correct URL ) :
import React from 'react';
import { Table, Button } from 'semantic-ui-react';
import "semantic-ui-css/components/table.min.css";
//import Metadata from './Metadata';
import classes from '../style/Form.module.css';
import Time from '../time/time';
import { useHistory } from 'react-router-dom';
import Metadata from './Metadata';
const AllMetadataTable = ({ allMetadata, metadataFilter, issLoading }) => {
const history = useHistory();
// sorted by time created - newest first
const allMetadataSorted = [...allMetadata].sort((a, b) => {
return new Date(b.TimestampCreated) - new Date(a.TimestampCreated);
});
// open details page for wanted metadata
const openDetailsPage = (key) => {
history.push({
pathname: 'data',
search: `?id=${allMetadata[key].DObjectId}`
})
};
return (
<>
{!issLoading &&
<Table celled fixed singleLine
style={{
width : '60rem',
marginLeft: '-10rem',
}} >
<Table.Header>
<Table.Row>
<Table.HeaderCell>Creator</Table.HeaderCell>
<Table.HeaderCell>Host</Table.HeaderCell>
<Table.HeaderCell>Domain</Table.HeaderCell>
<Table.HeaderCell>Format</Table.HeaderCell>
<Table.HeaderCell>Time Created</Table.HeaderCell>
<Table.HeaderCell
style={{
width : '4rem',
}}>Details</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{allMetadataSorted.map((metadata) => (
<React.Fragment key={metadata.key}>
<Table.Row>
<Table.Cell>{metadata.OrgIdCreator}</Table.Cell>
<Table.Cell>{metadata.OrgIdHost}</Table.Cell>
<Table.Cell>{metadata.TagsDomain}</Table.Cell>
<Table.Cell>{metadata.DataFormatId}</Table.Cell>
<Table.Cell>{Time(metadata.TimestampCreated)}</Table.Cell>
<Table.Cell>
{/** open/close metadata */}
<Button className={classes.uichange}
style ={{
border: 'none',
borderRadius: '0px',
color: 'white',
cursor: 'pointer',
backgroundColor: '#19a47c',
margin: '0 1rem',
fontSize : 22 }}
onClick={() => openDetailsPage(metadata.key) }>
+
</Button>
</Table.Cell>
</Table.Row>
</React.Fragment>
))}
</Table.Body>
</Table>
}
</>
);
};
export default AllMetadataTable;`
Here is my Metadata.js component which I wanna show in a new page when + button in a table is clicked ( id I am getting is corrrect and it is displaying in a list correctly but all other fields are empty; how can I access other fields and display them ?) :
`import React from 'react';
import classes from '../style/SingleData.module.css';
import list from '../style/List.module.css';
import { Button } from 'semantic-ui-react';
//import styles from '../UI/Messages.module.css';
import LoadingSpinner from '../UI/LoadingSpinner';
import Time from '../time/time';
import { useLocation } from 'react-router-dom';
/** import ORG_NAME */
const ORG = process.env.REACT_APP_ORG_NAME;
const Metadata = (props) => {
const { search } = useLocation();
const id = new URLSearchParams(search).get('id');
console.log(id);
// close metadata
const stopReadingDataHandler = () => {
props.onClose()
};
return (
<>
<ul className={list.tableList} >
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong> Data Id: </strong> {id}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong> Doc Type Code: </strong> {props.DocTypeCode}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong> Data Format: </strong> {props.DataFormatId}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong>Creator: </strong> {props.OrgIdCreator}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong> Host: </strong> {props.OrgIdHost}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong>Tags Content: </strong> {props.TagsContent}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong>Domain: </strong> {props.TagsDomain}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong>Time Created: </strong> {Time(props.TimestampCreated)}
</li>
<li style={{ borderRadius: '0px' }} className={classes.data}>
<strong>Time Updated: </strong> {Time(props.TimestampUpdated)}
</li>
{ /** display Cancel button if you are Creator or Host */}
{!props.isLoading && (props.OrgIdCreator === ORG || props.OrgIdHost === ORG) && props.transferCheckStatus === false ?
<div style={{ justifyContent: 'flex-start' }}
className={classes.Form__actions}>
<Button className={classes.uichangedelete}
style ={{
border: 'none',
borderRadius: '3px',
color: 'white',
cursor: 'pointer',
backgroundColor: 'red',
margin: '10px',
fontSize : 22 }}
type= 'button'
content='Cancel'
onClick={ stopReadingDataHandler }
/>
</div>
: null }
{/** display loading spinner if loading */}
{props.isLoading && <LoadingSpinner />}
</ul>
</>
);
};
export default Metadata;
I tried using props inside FindAllMetadata, inside AllMetadataTable; I created other page Details.js in same folder as FindAllMetadata ( pages folder ) ; I tried useHistory, useLocation, useParams etc. `
In your openDetailsPage function you are passing only the id to the Metadata component:
const openDetailsPage = (key) => {
history.push({
pathname: 'data',
search: ?id=${allMetadata[key].DObjectId}
})
};
You are using history.push method with the following parameters:
pathname => which is '/data/ for Metadata component
search => query to url (NOTE: Here you are passing only the id)
Try adding:
state => an object to pass to the Metadata components:
history.push({
pathname: 'data',
search: `?id=${allMetadata[key].DObjectId}`,
state: {item: allMetadata[key]}
})
Then access it on the Metadata component by using:
props.location.state.item
Related
in react I want to run an application-related react-router; that is fine; the problem is that specific code, such as "react-router-dom": "6.8.1," does not run in the latest react version and throws an error.
"Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: undefined. You
likely forgot to export your component from the file it's defined in,
or you might have mixed up default and named imports."
But if you change the version, it's working fine, like in "react-router-dom": "5.2.1", This code is working fine, ,so my question is in "react-router-dom": :6.8.1 How to successfully run that code?in my App.js in code structure which thing should I have to change that code will run perfectly in 6.8.1 too?
import { useState } from "react";
import {
BrowserRouter as Router,
generatePath,
Switch,
Route,
useHistory,
useParams
} from "react-router-dom";
const products = [
{
id: "1",
name: "Product 1"
},
{
id: "2",
name: "Product 2"
},
{
id: "3",
name: "Product 3"
}
];
const Products = () => {
const { id } = useParams();
console.log(id);
return (
<div>
<p>Lorem Ipsum</p>
<p>Id: {id}</p>
</div>
);
};
const Home = () => {
const [id, setId] = useState();
const history = useHistory();
console.log(history)
const handleProceed = (e) => {
id && history.push(generatePath("/products/:id", { id }));
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div>
{products.map((product, i) => (
<button
key={i}
onClick={(e) => {
setId(product.id);
}}
>
{product.name}
</button>
))}
</div>
<button onClick={handleProceed} style={{ width: "250px" }}>
Click
</button>
</div>
);
};
export default function App() {
return (
<div className="App">
<header>Heading</header>
<Router>
<Switch>
<Route path="/products/:id">
<Products />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</div>
);
}
For React Router v6, useNavigate should be used instead of useHistory (which no longer exists).
const navigate = useNavigate();
// ...
id && navigate(generatePath("/products/:id", { id }));
react-router#6 brought with it a ton of breaking changes. You should review the Upgrading from v5 migration guide for the full details.
The Switch component was replaced by the Routes component which is required to directly wrap Routes you are rendering, and the useHistory hook was replaced by the useNavigate hook.
import { useState } from "react";
import {
BrowserRouter as Router,
generatePath,
Routes,
Route,
useNavigate,
useParams
} from "react-router-dom";
useNavigate returns a navigate function instead of a "history" object. Call navigate to issue imperative navigation actions.
const Home = () => {
const [id, setId] = useState();
const navigate = useNavigate();
const handleProceed = () => {
navigate(generatePath("/products/:id", { id }));
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div>
{products.map((product) => (
<button
key={product.id}
onClick={() => {
setId(product.id);
}}
>
{product.name}
</button>
))}
</div>
<button
type="button
disabled={!id}
onClick={handleProceed}
style={{ width: "250px" }}
>
Click
</button>
</div>
);
};
The Route component API/props changed, all the routed content is rendered on the Route component's element prop.
export default function App() {
return (
<div className="App">
<header>Heading</header>
<Router>
<Routes>
<Route path="/products/:id" element={<Products />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
</div>
);
}
Please See the >>
I want to update Products by clicking on Category Name the MegaMenu, But it does not change. I use redux for state management. Please help me !!
This is MegaMenu Component
import MenuIcon from '#mui/icons-material/Menu';
import './MegaMenu.css';
import { NavLink } from 'react-router-dom';
import ArrowRightIcon from '#mui/icons-material/ArrowRight';
const categories = [
"Sunglass",
"Footwear",
"Men Watch",
"Women Watch",
"Computer",
"SmartPhones",
"Camera",
];
const MegaMenu = () => {
return (
<div className='mega_menu_component'>
<div className='mega_menu_Items'>
<div className='mega_menus'>
<MenuIcon className='mega_menu_icon' style={{ fontSize: '40px', color: '#fff', cursor: 'pointer' }} />
<div class="menu_link_content">
<NavLink className="menu_name" to="/">Home</NavLink>
<NavLink className="menu_name" to="/products">Shop <ArrowRightIcon style={{ fontSize: 'medium' }} />
<ul className="categoryMenu">
{categories.map((categoryName) => (
<li className="categoryMenu-link" > <NavLink style={{ textDecoration: 'none', color: '#000' }} key={categoryName} to={`/products/${categoryName}`}>{categoryName}</NavLink></li>
))}
</ul>
</NavLink>
<NavLink className="menu_name" to="/contact">Contact</NavLink>
<NavLink className="menu_name" to="/about">About</NavLink>
</div>
</div>
</div>
</div>
);
};
export default MegaMenu;
This is Products component
import React, { Fragment, useEffect, useState } from "react";
import "./Products.css";
import { useSelector, useDispatch } from "react-redux";
import { clearErrors, getProduct } from "../../actions/productAction";
import Loader from "../layout/Loader/Loader";
import ProductCard from "../Home/ProductCard";
import Pagination from "react-js-pagination";
import Slider from "#material-ui/core/Slider";
import { useAlert } from "react-alert";
import Typography from "#material-ui/core/Typography";
import MetaData from "../layout/MetaData";
import { useParams } from "react-router";
import { NavLink, useLocation } from "react-router-dom"
const categories = [
"Sunglass",
"Footwear",
"Men Watch",
"Women Watch",
"Computer",
"SmartPhones",
"Camera",
];
const Products = ({ match }) => {
const dispatch = useDispatch();
const urlLocation = useLocation();
const categoryName = urlLocation.pathname.split('/')[2];
const alert = useAlert();
const [currentPage, setCurrentPage] = useState(1);
const [price, setPrice] = useState([0, 25000]);
// const [category, setCategory] = useState(categoryName);
const [category, setCategory] = useState('');
const [ratings, setRatings] = useState(0);
const {
products,
loading,
error,
productsCount,
resultPerPage,
filteredProductsCount,
} = useSelector((state) => state.products);
const keyword = match.params.keyword;
const setCurrentPageNo = (e) => {
setCurrentPage(e);
};
const priceHandler = (event, newPrice) => {
setPrice(newPrice);
};
let count = filteredProductsCount;
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
dispatch(getProduct(keyword, currentPage, price, category, ratings));
}, [dispatch, keyword, currentPage, price, category, ratings, alert, error]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
<MetaData title="PRODUCTS -- ECOMMERCE" />
<h2 className="productsHeading">Products</h2>
<div className="products">
{products &&
products.map((product) => (
<ProductCard key={product._id} product={product} />
))}
</div>
<div className="filterBox">
<Typography>Price</Typography>
<Slider
value={price}
onChange={priceHandler}
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={25000}
/>
<Typography>Categories</Typography>
<ul className="categoryBox">
{categories.map((category) => (
<li className="category-link" >
<NavLink style={{ textDecoration: 'none', color: 'black' }} key={category} onClick={() => setCategory(category)} to={`/products`}>{category}</NavLink>
</li>
))}
</ul>
<fieldset>
<Typography component="legend">Ratings Above</Typography>
<Slider
value={ratings}
onChange={(e, newRating) => {
setRatings(newRating);
}}
aria-labelledby="continuous-slider"
valueLabelDisplay="auto"
min={0}
max={5}
/>
</fieldset>
</div>
{resultPerPage < count && (
<div className="paginationBox">
<Pagination
activePage={currentPage}
itemsCountPerPage={resultPerPage}
totalItemsCount={productsCount}
onChange={setCurrentPageNo}
nextPageText="Next"
prevPageText="Prev"
firstPageText="1st"
lastPageText="Last"
itemClass="page-item"
linkClass="page-link"
activeClass="pageItemActive"
activeLinkClass="pageLinkActive"
/>
</div>
)}
</Fragment>
)}
</Fragment>
);
};
export default Products;
** When I want to click on the mega menu "Shop>>Category" name the URL would be like http://localhost:3000/products/Sunglass Here sunglass is the category name **
** Now I want to data update in the Product component (ProductCard Update) using the URL category part. But it does not work. **
I have this serch.js file. When I search and click on the li in the search result, I want to get redirected to <InnerDetail /> but the URL doesn't change or I don't get redirected to this page
but manualy if I type in the URL localhost/detiled/8 I am redirected to to <InnerDetail /> with id as 8
import React from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import { useState, useEffect } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";
const initialState = {
idaddProducts: "",
};
const Searchclients = () => {
const history = useHistory();
const [showResults, setShowResults] = React.useState(true);
const [poName, pnName] = React.useState(initialState);
const [showSerch, setShowSerch] = React.useState([]);
const [detail, setDetail] = useState(false);
const [inputValue, setInputValue] = React.useState("");
const [filteredSuggestions, setFilteredSuggestions] = React.useState([]);
const [selectedSuggestion, setSelectedSuggestion] = React.useState(0);
const [displaySuggestions, setDisplaySuggestions] = React.useState(false);
const suggestions = [];
showSerch.forEach(function (data) {
suggestions.push(data);
});
const onChange = (event) => {
const value = event.target.value;
setInputValue(value);
setShowResults(false);
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.firstname
.toString()
.toLowerCase()
.includes(value.toLowerCase()) ||
suggestion.id.toString().toLowerCase().includes(value.toLowerCase())
);
setFilteredSuggestions(filteredSuggestions);
setDisplaySuggestions(true);
};
const onSelectSuggestion = (index) => {
setSelectedSuggestion(index);
setInputValue(filteredSuggestions[index]);
setFilteredSuggestions([]);
setDisplaySuggestions(false);
};
const SuggestionsList = (props) => {
// console.log(props);
const {
suggestions,
inputValue,
onSelectSuggestion,
displaySuggestions,
selectedSuggestion,
} = props;
if (inputValue && displaySuggestions) {
if (suggestions.length > 0) {
return (
<ul className="suggestions-list" style={styles.ulstyle}>
{suggestions.map((suggestion, index) => {
// console.log(suggestions);
const isSelected = selectedSuggestion === index;
const classname = `suggestion ${isSelected ? "selected" : ""}`;
return (
<Link to={`/detiled/${suggestion.id}`}> //this link dont work
<li
style={styles.listyle}
// onMouseOver={{ background: "yellow" }}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
</Link>
);
})}
</ul>
);
} else {
return <div>No suggestions available...</div>;
}
}
return <></>;
};
useEffect(() => {
axios
.get("all-doctors-list/")
.then((res) => {
const data = res.data;
// pnName(data.data);
// var stringdata = data;
setShowSerch(data);
//console.log(stringdata);
});
// setShowSerch(data);
}, []);
return (
<>
<div className="note-container" style={styles.card}>
<div style={styles.inner}>
<p style={{ textAlign: "left" }}>Search Doctors</p>
<form className="search-form" style={{}}>
{showResults ? (
<FontAwesomeIcon
style={{ marginRight: "-23px" }}
icon={faSearch}
/>
) : null}
<input
onChange={onChange}
value={inputValue}
style={styles.input}
type="Search"
/>
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
</form>
</div>
</div>
</>
);
};
export default Searchclients;
Navigator.js
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import InnerDetail from "./client/doctor/components/innerto_detail.js";
class Navigator extends React.Component {
render() {
return (
<Router>
<div>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/detiled/:id">
<InnerDetail />
</Route>
</Switch>
</div>
</Router>
);
}
}
function Home() {
return (
<div style={{ paddingTop: "20%", textAlign: "center" }}>
<h1>Home</h1>
</div>
);
}
export default Navigator;
when i hover on the<li> the bottom of the browser the path is showing currectly
i think the problem is here i tried another links here nothing is working here
<Link to={`/detiled/${suggestion.id}`}> //this link dont work
<li
style={styles.listyle}
// onMouseOver={{ background: "yellow" }}
key={index}
className={classname}
>
{suggestion.firstname}
</li>
</Link>
i tryed another link under the flowing code
a button called hello and that works i think the probelm is with the return
<SuggestionsList
inputValue={inputValue}
selectedSuggestion={selectedSuggestion}
onSelectSuggestion={onSelectSuggestion}
displaySuggestions={displaySuggestions}
suggestions={filteredSuggestions}
/>
<Link
to={{
pathname: `/detiled/5`,
}}
>
<button>hello</button>
</Link>
try using
<Route path="/detiled/:id" component={InnerDetail} />
instead of
<Route path="/detiled/:id">
<InnerDetail />`
in Navigator.js
and
Route exact path="/">
<Home />
did you created any Home component, and not imported that in Navigator.js
<Link to={"/detiled/"+ suggestion.id}><li>...</li></Link>
this worked for me
I'm trying to create path for component like this "/products?id=uniqueId" for my React Js Project. Unique id is taken from database and depends on which product was chosen. My code is
CodeSandbox
App.js
import "./styles.css";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Products from "./Products"
import Home from "./Home"
export default function App() {
return (
<div className="App">
<Router>
<Switch>
<Route exact path="/">
{" "}
<Home/>
</Route>
<Route exact path="/products">
{" "}
<Products />
</Route>
</Switch>
</Router>
</div>
);
}
Home.js
import "./styles.css";
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
const products = [
{
id: "1",
name: "Product 1"
},
{
id: "2",
name: "Product 2"
},
{
id: "3",
name: "Product 3"
}
];
export default function Home() {
const [id, setId] = useState();
const history = useHistory();
const handleProceed = (e) => {
// console.log(id, "home");
history.push("/products", { id });
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div>
{products.map((product) => {
return (
<button
onClick={(e) => {
setId(product.id);
}}
>
{product.name}{" "}
</button>
);
})}
</div>
<button onClick={handleProceed} style={{ width: "250px" }}>
{" "}
Proceed
</button>
</div>
);
}
Product.js
import "./styles.css";
import { useLocation } from "react-router-dom";
export default function Home() {
const location = useLocation();
const { id } = location.state || { id: "none" };
console.log(id);
return (
<div>
<p> Lorem Ipsum</p>
</div>
);
}
Idea is when user presses any button with product name in < Home /> component, page should be redirected to < Product /> component and path should be "/products?id=uniqueId" where uniqueId = to product.id. I was able to pass product.id from < Home /> to < Product /> using useHistory() and useLocation(), but I don't know how to make path display selected id. For example if user clicks Product 1 button, path to < Product /> should be "/products?id=1", if Product 2 was chosen path should reflect that as "/products?id=2" and so on.
Any help and suggestions are greatly appreciated.
I suggest making the product id part of the path versus placing it in the query string. As part of the path it requires almost nothing from your code to access whereas if you used the query string you would then need to parse the entire query string into a Map of key-value pairs.
Define your path to include an id
<Route path="/products/:id">
<Products />
</Route>
On the products page use useParams react hook to access the id Route match param.
const { id } = useParams();
Generate the path with id parameter
const handleProceed = (e) => {
history.push(generatePath("/products/:id", { id }));
};
or if you prefer it raw
const handleProceed = (e) => {
history.push(`/products/${id}`));
};
I further suggest optimizing your routing by reordering your Route components to specify the more specific paths first. This allows you to avoid passing the exact prop to every route.
<Router>
<Switch>
<Route path="/products/:id">
<Products />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
Demo
full demo code:
import { useState } from "react";
import {
BrowserRouter as Router,
generatePath,
Switch,
Route,
useHistory,
useParams
} from "react-router-dom";
const products = [
{
id: "1",
name: "Product 1"
},
{
id: "2",
name: "Product 2"
},
{
id: "3",
name: "Product 3"
}
];
const Products = () => {
const { id } = useParams();
console.log(id);
return (
<div>
<p>Lorem Ipsum</p>
<p>Id: {id}</p>
</div>
);
};
const Home = () => {
const [id, setId] = useState();
const history = useHistory();
const handleProceed = (e) => {
id && history.push(generatePath("/products/:id", { id }));
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div>
{products.map((product, i) => (
<button
key={i}
onClick={(e) => {
setId(product.id);
}}
>
{product.name}
</button>
))}
</div>
<button onClick={handleProceed} style={{ width: "250px" }}>
Proceed
</button>
</div>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Router>
<Switch>
<Route path="/products/:id">
<Products />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</div>
);
}
react-router-dom#6 Compliant Version:
import { useState } from "react";
import {
BrowserRouter as Router,
generatePath,
Routes,
Route,
useNavigate,
useParams,
} from "react-router-dom";
const products = [
{
id: "1",
name: "Product 1"
},
{
id: "2",
name: "Product 2"
},
{
id: "3",
name: "Product 3"
}
];
const Products = () => {
const { id } = useParams();
console.log(id);
return (
<div>
<p>Lorem Ipsum</p>
<p>Id: {id}</p>
</div>
);
};
const Home = () => {
const [id, setId] = useState();
const navigate = useNavigate();
const handleProceed = (e) => {
id && navigate(generatePath("/products/:id", { id }));
};
return (
<div
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div>
{products.map((product, i) => (
<button
key={i}
onClick={(e) => {
setId(product.id);
}}
>
{product.name}
</button>
))}
</div>
<button onClick={handleProceed} style={{ width: "250px" }}>
Proceed
</button>
</div>
);
};
export default function App() {
return (
<div className="App">
<Router>
<Routes>
<Route path="/products/:id" element={<Products />} />
<Route path="/" element={<Home />} />
</Switch>
</Router>
</div>
);
}
So my react code does not execute as logic would suggest:
I am trying to create a page that resembles an email app.
I have components in React which I import into my message(main) component (e.g. Inbox component, send mail component etc). I have a useState, which is set to 'inbox' as default and a switch statement which checks which value is in the state and returns the component matching that case. I also have a onclick function which changes the state value based on which email tab you want to view (e.g. Inbox or trash etc). When the state changes the component should run the switch statement in my page render and display the correct component in the main component, which is does most of the time.
My problem comes when I keep switching between the tabs which have components it at some point goes to the default of the switch statement which does not make sense because either of the values should have passed.
Please help
import React, {useState} from 'react';
// Components to be displayed
import Inbox from './Inbox';
import SendMessage from './SendMessage';
// Other components on the page which you can ignore
import Navbar from '../Layout/Navbar';
import Copyright from '../Layout/Copyright';
import SendMail from './SendMail';
//Material ui for styling
import AppBar from '#material-ui/core/AppBar';
import CssBaseline from '#material-ui/core/CssBaseline';
import Divider from '#material-ui/core/Divider';
import Drawer from '#material-ui/core/Drawer';
import Hidden from '#material-ui/core/Hidden';
import IconButton from '#material-ui/core/IconButton';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import MailIcon from '#material-ui/icons/Mail';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import Link from '#material-ui/core/Link';
import ControlPointIcon from '#material-ui/icons/ControlPoint';
const drawerWidth = 240;
const list = [
{id: 1, title: 'Singing lessons', message: 'We are practicing at 5:00'},
{id: 2, title: 'Meeting', message: 'Hi Guys, we can meet during lunch at Nandos'},
{id: 3, title: 'New Product release', message: 'Hi guys, we are encouraging everyone to buy in on the new product'}
]
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex'
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
zIndex: '0'
}
},
appBar: {
[theme.breakpoints.up('sm')]: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
},
},
menuButton: {
marginRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
display: 'none',
},
},
// necessary for content to be below app bar
toolbar: theme.mixins.toolbar,
drawerPaper: {
width: drawerWidth,
},
content: {
flexGrow: 1,
padding: theme.spacing(3)
},
listItemStyle: {
marginTop: '50px'
},
newMessage: {
fontSize: '40px',
backgroundColor: '#64b5f6',
borderRadius: '50px',
color: '#fff',
position: 'absolute',
bottom: '50px',
right: '50px',
'&:hover': {
backgroundColor: '#1976d2'
}
}
}));
// Main component
const Message = (props) => {
const { window } = props;
const classes = useStyles();
const theme = useTheme();
const [mobileOpen, setMobileOpen] = useState(false);
const [tabTracker, setTabTracker] = useState('Inbox');
const renderSwitch = (param) => {
switch(param) {
case 'Inbox':
return <Inbox />;
case 'Send email':
return <SendMail />;
case 'Drafts':
return 'Draft';
case 'Trash':
return 'Trash';
case 'Spam':
return 'Spam';
case 'newMessage':
return <SendMessage />;
default:
return 'foo';
}
}
const tabControl = (e) => {
setTabTracker(e.target.firstChild.data);
//renderSwitch(tabTracker);
}
const newMsg = (e) => {
setTabTracker(e.target.attributes[5].value);
//renderSwitch(tabTracker);
}
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={tabControl}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);
const container = window !== undefined ? () => window().document.body : undefined;
return (
<React.Fragment>
<span>
<Navbar />
</span>
<div className={classes.root}>
<CssBaseline />
<nav className={classes.drawer} aria-label="mailbox folders">
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Hidden smUp implementation="css">
<Drawer
container={container}
variant="temporary"
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={mobileOpen}
onClose={handleDrawerToggle}
classes={{
paper: classes.drawerPaper,
}}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer
classes={{
paper: classes.drawerPaper,
}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
<main className={classes.content}>
<div className={classes.toolbar} />
{renderSwitch(tabTracker)}
<Link href="#">
<ControlPointIcon value="newMessage" primary="newMessage" className= {classes.newMessage} onClick={newMsg} />
</Link>
</main>
</div>
<Copyright />
</React.Fragment>
);
}
// ResponsiveDrawer.propTypes = {
// /**
// * Injected by the documentation to work in an iframe.
// * You won't need it on your project.
// */
// window: PropTypes.func,
// };
export default Message;
e.target gives you the element on which click was actually triggered, so when you have multiple elements as children within ListItem, its possible that you would have clicked on ListItemIcon and hence e.target is listItemIcon which was not your intention as it will give you an incorrect value for e.target.firstChild.data
So you could either use e.currentTarget which will give you the element on which the onClick listener is attached or you could simply pass on the information to tabControl by using an inline arrow function
const tabControl = (value) => {
setTabTracker(value);
}
const drawer = (
<div>
<div className={classes.toolbar} />
<Divider />
<List className={classes.listItemStyle}>
{['Inbox', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['Trash', 'Spam'].map((text, index) => (
<ListItem button key={text} onClick={() => tabControl(text)}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
);