I have a map of an objects. Each object is an item in a list. When I click on a concrete item it moves me to a specific route with that item's id. How to properly manage that concrete item's props inside new route?
Is this the way to go or there is better way?
export const Router = (): ReactElement => {
const [itemDetails, setItemDetails] = useState<Item>();
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route
index
element={<ItemList setItemDetails={setItemDetails} />}
/>
<Route
path="/item/:itemId"
element={<ItemDetails itemDetails={itemDetails} />}
/>
</Route>
</Routes>
</BrowserRouter>
);
};
So basically what I'm doing here is I'm passing state setter function to a list where my mapped items are stored so when I click on a concrete one I save its props to that function.
Then I just pass updated state to a new route where details of a concrete item should be displayed.
If you are asking if there's a better way than pushing some value from ItemList up to the parent state to be passed down to ItemDetails, then sure, a more React/react-router-dom way would be to hoist the items array from the ItemList component up to this parent component and pass the data down to children routes as props or via a context.
Example using props
export const Router = (): ReactElement => {
const [items, setItems] = useState<Array<Item>>([]);
... logic to populate items state ...
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route index element={<ItemList items={items} />} />
<Route path="/item/:itemId" element={<ItemDetails items={items} />} />
</Route>
</Routes>
</BrowserRouter>
);
};
ItemDetails
const ItemDetails = ({ items }) => {
const { itemId } = useParams();
const item = items.find(item => String(item.id) === itemId);
if (!item) {
return <div>No matching item found.</div>;
}
return (
... render item out to JSX
);
};
Example using Outlet context
export const Router = (): ReactElement => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Root />}>
<Route index element={<ItemList />} />
<Route path="/item/:itemId" element={<ItemDetails />} />
</Route>
</Routes>
</BrowserRouter>
);
};
Root
const Root = () => {
const [items, setItems] = useState<Array<Item>>([]);
... logic to populate items state ...
return (
...
<Outlet context={{ items }} />
...
);
};
ItemList
const ItemList = () => {
const { items } = useOutletContext();
return (
... map items to JSX
);
};
ItemDetails
const ItemDetails = () => {
const { items } = useOutletContext();
const { itemId } = useParams();
const item = items.find(item => String(item.id) === itemId);
if (!item) {
return <div>No matching item found.</div>;
}
return (
... render item out to JSX
);
};
Related
I want to build a car list app that will show a list of cars, and when I click on a car's details button, it will route it to another component/page that will show the details of cars.
I can get the key (vin for me) but I want to get the details of the car for each key(vin) on this page.It's like http://localhost:3000/cars/WAUZZZ4G6FN052847= key. So when a car's key will show up, all details will come due to their key number. Thank you.
index.html
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter, Routes, Route, Outlet, } from "react-router-dom";
import DummyComponent from "./components/DummyComponent";
import CarDetails from "./pages/CarDetails";
import { Home } from "#mui/icons-material";
import Cars from "./pages/Cars";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/cars" element={<Outlet />} >
<Route path="list" element={<Cars />} />
<Route path=":vin" element={<CarDetails />} />
</Route>
<Route path="Home" element={<Home />} />
<Route path="DummyComponent" element={<DummyComponent />} />
</Routes>
</BrowserRouter>
);
Cars.js
import axios from "axios";
import React, { useEffect, useState } from "react";
import List from '#mui/material/List';
import ListItem from '#mui/material/ListItem';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemText from '#mui/material/ListItemText';
import { Link } from "react-router-dom";
const App = () => {
const [cars, setCars] = useState([])
const getCarData = async () => {
try {
const data = await axios.get("https://react-challenge-api.azurewebsites.net/vehicles")
setCars(data.data)
}
catch (e) {
console.log(e)
}
}
useEffect(() => {
getCarData()
}, [])
return (
<div className="App">
<List sx={{ width: '100%', maxWidth: 600, bgcolor: 'background.paper' }}>
{cars.map((car) => (
<ListItemButton key={car.vin}>
<ListItem
key={car.vin}
disableGutters
secondaryAction={
<ListItemButton >
<Link to={`/cars/${car.vin}`}>details</Link>
</ListItemButton>
}
>
<ListItemText key={car.vin} primary={car.model_variant} />
</ListItem>
</ListItemButton>
))
}
</List >
</div >
);
};
export default App;
CarDetails.js (I want to show each data in this component, I used params but I don't know how to get data due to params.
import { useParams } from "react-router-dom";
const CarDetails = () => {
let params = useParams();
return (
<>
<h1>car</h1>
<ul>
this is your {params.vin}
</ul>
</>
)
}
export default CarDetails;
I would suggest moving the car data fetching into a layout route and pass the cars state down in an Outlet context.
Example:
Cars - Handles fetching the car data and passes the cars state along to nested routes via the Outlet component's context.
const Cars = () => {
const [cars, setCars] = useState([]);
const getCarData = async () => {
try {
const data = await axios.get(
"https://react-challenge-api.azurewebsites.net/vehicles"
);
setCars(data.data);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getCarData();
}, []);
return <Outlet context={{ cars }} />;
};
CarList - Reads the cars state from the Outlet context and renders the list.
const CarList = () => {
const { cars } = useOutletContext();
return (
<List sx={{ width: "100%", maxWidth: 600, bgcolor: "background.paper" }}>
{cars.map((car) => (
<ListItemButton key={car.vin}>
<ListItem
key={car.vin}
disableGutters
secondaryAction={
<ListItemButton>
<Link to={`/cars/${car.vin}`}>details</Link>
</ListItemButton>
}
>
<ListItemText key={car.vin} primary={car.model_variant} />
</ListItem>
</ListItemButton>
))}
</List>
);
};
CarDetails - Takes the vin route path param and the cars array from the outlet context and searches for a matching car object.
const CarDetails = () => {
const { vin } = useParams();
const { cars } = useOutletContext();
const car = cars.find((car) => car.vin === vin);
if (!car) {
return "No car matches this VIN";
}
return (
<>
<h1>{car.model_variant}</h1>
<ul>
<li>Body: {car.body_type}</li>
<li>Doors: {car.doors}</li>
<li>Fuel: {car.fuel_type}</li>
<li>VIN: {car.vin}</li>
<li>Registration #: {car.regno}</li>
<li>Transmission: {car.transmission_type}</li>
</ul>
</>
);
};
Routes
<Routes>
<Route path="/" element={<App />} />
<Route path="/cars" element={<Cars />}> // <-- layout route renders outlet
<Route path="list" element={<CarList />} />
<Route path=":vin" element={<CarDetails />} />
</Route>
<Route path="Home" element={<Home />} />
<Route path="DummyComponent" element={<DummyComponent />} />
</Routes>
This question already has an answer here:
Get id using useParams hook in functional component - react router dom v6
(1 answer)
Closed 10 months ago.
I'm updating an outdated tutorial project using react hooks and react router v6. I want to pass the id in the path url to the component property..
function App() {
const findPalette = (id) => {
return colorsData.find((palette) => palette.id === id);
};
return (
<Routes>
<Route path="/" element={<h1>Palette List goes here!</h1>} />
<Route path="/palette/:id" element={<Palette palette={generatePalette(findPalette(requiredID))} />} />
</Routes>
);
}
What should I do in place of 'requiredID' to get it done?
Here is the code in the tutorial
<Route
exact
path='/palette/:id'
render={routeProps => <Palette palette={generatePalette(this.findPalette(routeProps.match.params.id))} />}
/>
I found useParams() in the documentation, but it is used inside the function component passed to the element property
function ProfilePage() {
// Get the userId param from the URL.
let { userId } = useParams();
// ...
}
function App() {
return (
<Routes>
<Route path="users">
<Route path=":userId" element={<ProfilePage />} />
<Route path="me" element={...} />
</Route>
</Routes>
);
}
Create a custom hook that wraps useParams:
const usePalette = () => {
const { userId: id } = useParams();
return useMemo(() => colorsData.find(palette => palette.id === id), []);
}
function ProfilePage() {
const palette = usePalette();
// ...
}
If you can't change ProfilePage create a wrapper component (ProfilePageWrapper) that uses the hook, and let it render ProfilePage.
const ProfilePageWrapper () => {
const palette = usePalette();
return <ProfilePage palette={palette} />;
}
Then use the wrapper in the route:
<Route path="/palette/:id" element={<ProfilePageWrapper />} />
I have a router:
<Switch>
<Route exact path="/">
<CustomPaddingContainer padding="0 0.5em 1.5em">
<TableViewComponent columns={tableAccessors} />
</CustomPaddingContainer>
</Route>
<Route path="/new-objective">
<AddNewObjectiveComponent onSubmit={onSubmitObjective} onCancel={onCancel} />
</Route>
<Route path="/new-kr">
<AddNewKrComponent onSubmit={onSubmitKR} onCancel={onCancel} />
</Route>
<Route path="/okr-details/:id">
<OkrDetailsWithParams />
</Route>
</Switch>
and I want to pass specific data from specific component to one of this Route when specific button will be clicked. to be more precise, I have this component:
const AddOKRButtons: FC<AddOKRButtonsProps> = ({ parentObjectiveId }) => {
const history = useHistory();
const onAddOkrButtonClick = () => {
history.push('/new-objective', { parentObjectiveId: parentObjectiveId });
};
const onAddKrButtonClick = () => {
history.push('/new-kr', { parentObjectiveId: parentObjectiveId });
};
return (
<OkrDetailsChildrenCardsButtonContainerCentered>
<ButtonGroup>
<LinkButton to="/new-objective" appearance="default" onClick={onAddOkrButtonClick}>
Add a new sub-objective
</LinkButton>
<LinkButton to="/new-kr" appearance="default" onClick={onAddKrButtonClick}>
Add a new key-result
</LinkButton>
</ButtonGroup>
</OkrDetailsChildrenCardsButtonContainerCentered>
);
};
Im trying to pass the **parentObjectiveId** which is coming from props to the /new-objective page or /new-kr page in order what button was clicked. After that Im trying to get that data in component where it should be with useLocation hook:
export const AddNewObjectiveComponent: FC<NonNullable<AddNewOKRProps>> = props => {
const location = useLocation();
console.log(location);
return(<div></div>)
}
and unfortunately i got undefined in the state key, where the data is probably should be:
Try to push history route like
history.push({
pathname: '/new-objective',
state: { parentObjectiveId: parentObjectiveId }
});
I hope it will be work for you. Thanks!
so in react I have an App component that is rendering several child components like this:
App
render() {
return (
//JSX inside
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Courses} />
<Route exact path="/courses/create" component={() => <CreateCourse email={this.state.emailAddress} pass={this.state.password} />} />
<Route exact path="/courses/:id/update" component={() => <UpdateCourse email={this.state.emailAddress} pass={this.state.password} />} />
<Route exact path="/courses/:id" component={() => <CourseDetail email={this.state.emailAddress} pass={this.state.password} />} />
<Route exact path="/signin" component={ () => <UserSignIn signIn={this.signIn}/>} /> {/*pass in the signIn() in a prop called signIn to the UserSignIn component*/}
<Route exact path="/signup" component={UserSignUp} />
{/* <Route exact path="/signout" component={UserSignOut} /> */}
</Switch>
</div>
</BrowserRouter>
);
}
In this component I have params so that I am able to see a course by its id:
CourseDetail
componentDidMount() {
const {match: { params }} = this.props; //I used a code snippet from this video https://scotch.io/courses/using-react-router-4/route-params
//fetch data from API
axios
.get(`http://localhost:5000/api/courses/${params.id}`)
.then(results => {
//results param came back as data from api
this.setState({
//set state by setting the courses array to hold the data that came from results
course: results.data,
user: results.data.user
});
//console.log(results); //By console logging I was able to see that I am getting each individual course's info in the data object
});
}
//this method will be for deleting a course
handleDelete() {
const { match: { params }, history } = this.props;
axios.delete(`http://localhost:5000/api/courses/${params.id}`, {
auth: {
username: this.props.email,
password: this.props.pass
}
}).then(() => {
history.push("/"); //I used the history object and have it push to the homepage, that way every time I delete a course I am redirected to (/) afterwards
});
}
the error I am getting when I try to navigate to the CourseDetail component that uses params is:
can someone help?
You need to pass props like this read here
component={props => <CourseDetail {...props} email={this.state.emailAddress} pass={this.state.password} />} />
The props passed to courseDetails component do not have any prop name match and in your componentDidMount you're doing this
const {match: { params }} = this.props;
Here match will be undefined so you can access params
You can understand by this example
let a = {a:{b:1}}
let {x:{b,}} = a
The above code is same as below
"use strict";
var a = {
a: {
b: 1
}
};
var b = a.x.b;
So eventually here if during destructuring if you don't have match as params you're trying to access
(this.props.match).params
|
|__________ This is undefined you end up `undefined.params`
Match is not defined because you didn't pass it down the component as props.
To do that
<Route exact path="/courses/:id/update" component={(routeProps) => <UpdateCourse email={this.state.emailAddress} pass={this.state.password} routeProps = {routeProps} />} />
you can then get your Match property via
routeProps.
const {match} = this.routeProps;
Or simply use the property spread notation which will spread out the properties in routeProps as discrete properties in your component.
Example,
<Route exact path="/courses/:id/update" component={(routeProps) => <UpdateCourse email={this.state.emailAddress} pass={this.state.password} {...routeProps} />} />
This is equivalent to writing-
<Route exact path="/courses/:id/update" component={(routeProps) => <UpdateCourse email={this.state.emailAddress} pass={this.state.password} Match = {this.routeProps.Match} Location = {this.routeProps.Location}/>} History = {this.routeProps.History />
When you write this
const {match: { params }} = this.props;
It means you are expecting props to have a match attribute like below:
params = this.props.match.params;
This is why you are getting the specified error.
If you want to assign a variable the props use this
const params = props;
Note: [If you surround a variable with bracket const {match} = props; it means you are expecting a key match in props.
I'm using react-router-dom and generate my routes dynamically from an array like so
Routes Data
const routes = [
{
path: '/',
exact: true,
component: () => <MyComponent />
}
}
Route Generation
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
})}
Render Prop
I found out about the render() prop I could define but even so how do I do this since the component is inside a variable
const props = this.props;
{routes.map((route, index) => {
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <??? {...props} />
/>
})}
How can I pass this.props during the route generation?
You could change your array to just include the component instead of a new function component that renders the component and you will get the props sent to it:
const routes = [
{
path: '/',
exact: true,
component: MyComponent
}
}
You can use any capitalized variable as a component, so you could also do this if you prefer:
{routes.map((route, index) => {
const Component = route.component;
return <Route
key={index}
path={route.path}
exact={route.exact}
render={(props) => <Component {...props} />
/>
})}