I want to build a page when from list of products I want to see product by ID. In my App file I have something like that:
<Route path={ROUTES.product} element={<Product id={1} />}>
but I want to replace static id with the value that comes from the selected product. In my Product file I have redirection to page with product but I don't know how I can pass the ID.
onClick={() => { navigate(`/products/${product?.id}`)}}
Any help would be appreciated.
The code you've provided appears to pass the id value in the path. It seems your question more about getting the Product component to have the correct id prop passed to it.
Given: path is "/products/:id"
Options to access the id param:
Use the useParams hook in Product to read the id route path param. This only works if Product is a function component.
import { useParams } from 'react-router-dom';
...
const { id } = useParams();
...
<Route path={ROUTES.product} element={<Product />} />
Use a wrapper component to use the useParams hook and inject the id value as a prop. This is useful if Product is not a function component.
import { useParams } from 'react-router-dom';
const ProductWrapper = () = {
const { id } = useParams();
return <Product id={id} />
};
...
<Route path={ROUTES.product} element={<ProductWrapper />} />
Create a custom withRouter Higher Order Component to use the useParams hook and inject a params prop.
import { useParams, ...other hooks... } from 'react-router-dom';
const withRouter = Component => props => {
const params = useParams();
... other hooks...
return (
<Component
{...props}
params={params}
... other hooks ...
/>
);
};
...
Wrap Product with withRouter HOC and access id param from props.params
props.params.id // or this.props.params
...
export default withRouter(Product);
...
<Route path={ROUTES.product} element={<Product />} />
so you already have the id with this
navigate(`/products/${product?.id}`)
just in Product component you can access id with
const { id } = useParams();
if you need to pass extra data you can try:
navigate(`/product/${product?.id}`, { state: { someExtradata }};
that you can access state in the component with :
const { state } = useLocation();
onClick={ () => Navegar(id) }
function Navegar(id) {
navigate('/products/id)
}
Related
Is there a way to get URL variables inside Route's element (and not the component itself), i.e.:
<Route
path='/book/:id'
element={<Book data={books[getUrlVarsSomehow().id]} />}
/>
This way, I can pass a single book to Book instead of passing the whole array books and choosing the correct one inside Book, which makes much more sense from a design perspective.
I am using react-router-dom v6.3.0
Yes, create a wrapper component that reads the route params (via useParams hook) and applies the filtering logic and passes the appropriate prop value.
Example:
import { useParams } from 'react-router-dom';
const BookWrapper = ({ books }) => {
const { id } = useParams();
return <Book data={books[id]} />;
};
...
<Route
path='/book/:id'
element={<BookWrapper books={books} />}
/>
react-router has a hook for that, useParams
Here is an example (from the react-router docs):
https://reactrouter.com/docs/en/v6/examples/route-objects
import { useParams } from "react-router-dom";
function Course() {
let { id } = useParams<"id">();
return (
<div>
<h2>
Welcome to the {id!.split("-").map(capitalizeString).join(" ")} course!
</h2>
<p>This is a great course. You're gonna love it!</p>
<Link to="/courses">See all courses</Link>
</div>
);
}
Live example: https://stackblitz.com/github/remix-run/react-router/tree/main/examples/route-objects?file=src/App.tsx
Create yourself a wrapping component you can use where ever you might need something from the URL:
import { useParams } from 'react-router-dom'
const WithParams = <T extends Record<string, string>>(props: {
children: (provided: T) => JSX.Element
}) => <>{props.children(useParams<T>())}</>
// usage
<Route path="/my/path/:someId">
<WithParams<{someId: string}>>
{({someId}) => (
<MyComponent id={someId} />
)}
</WithParams>
</Route>
You could create specific versions for specific path params, or create another that uses useLocation.search and parse out URL search param queries as well.
Im currently trying to send a state from the Link component to the Router component.
The router component looks like this:
<Route
exact
path='/banner/edit/:id'
>
{!user && <Redirect to="/login" />}
{user && org && <EditBanner uid={user.uid} org={org} />}
</Route>
This component is only rendered when the user is not null, and when the org is not null.
The EditBanner has a state called: bannerTitle:
export const EditBanner = ({ uid, org }) => {
// States
const [bannerTitle, setBannerTitle] = useState(null);
...
And when I am using Link to go to the component, I want to pass in the bannerTitle as a property to set the state in the EditBanner component. However, this is not having any effect on the Component.
<Link
to={{
pathname: "/banner/edit/" + banner.id,
state: { bannerTitle: "HELLO" }
}}
>
Edit
</Link>
Where can I get the passed props?
You can use the useLocation hook which provides a location object which has state:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import {useLocation} from "react-router-dom";
export const EditBanner = ({ uid, org }) => {
let location = useLocation();
useEffect(() => {
console.log(location.state);
}, [location])
// ...
}
Hopefully that helps!
I have an app built with React and Express Node, and in it I have 3 separate components. The first component is a gallery, where user selects an image to create a post with an background image. When button is clicked, user is taken to a form. The user will edit the inputs with some text and save the form which has a axios.post request to send the data to mongo db through express route. After saving user clicks view post that takes them to another component with axios.get request displaying image and input data to the user.
I have routes that have a unique http path to show the component that is active. My question is how can I map the routes to dynamically load the name of the image that comes from mongodb collection, instead of manually writing in the paths image name ie: path={"/getinputwaterfall/:id"}, path={"/getinputcross/:id"}, path={"/getinputfreedom/:id"} . I would like to have instead somthing like: path={"/getinput{urlName}/:id"}.
In the mongoDB collection I have a URL and name string array. The URL string is an http path from firebase and the name string are images names.
Is this possible to do?
Bellow is the code and my attempts to do this.
import React, {useState, useEffect} from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import "./index.css";
//gallery imports
import Cross from "./Components/Gallery/posts/Cross";
import Waterfall from "./Components/Gallery/posts/Waterfall";
import Freedom from "./Components/Gallery/posts/Freedom";
import CrossPost from "./Components/Gallery/get/CrossPost";
import WaterfallPost from "./Components/Gallery/get/WaterfallPost";
import FreedomPost from "./Components/Gallery/get/FreedomPost";
function App() {
const [name, setName] = useState([]);
const loadImage = async () => {
try {
let res = await axios.get("http://localhost:5000/geturls");
console.log(res.data)
setName(res.data.map(n=>n.name)); //array of names
} catch (error) {
console.log(error);
}
};
useEffect(() => {
loadImage();
}
,[]);
return (
<div className="App">
<Router>
<Switch>
{/* routes for gallery */}
<Route path={"/waterfall"} component={Waterfall} />
<Route path={"/cross"} component={Cross} />
<Route path={"/freedom"} component={Freedom} />
<Route path={"/getinputwaterfall/:id"} component={WaterfallPost} />
<Route path={"/getinputcross/:id"} component={CrossPost} />
<Route path={"/getinputfreedom/:id"} component={FreedomPost} />
{/* what I tryed to map */}
{name.filter(name => name === `${name}` ).map((urlName) => (
<Route exact path={`/getinputt/${urlName}`} component={CrossPost} />
))}
</Switch>
</Router>
</div>
);
}
export default App;
update: I applied the first option of the answer to my code for those who wish to see the complete solution: Note: I had to remove the '/' in /getinput/${name}/:id to make my code work! Thanks Drew!
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
return (
<div className="App">
<Router>
<Switch>
{imagePostRoutes.map(({ component, name }) => (
<Route
key={name} path={`/getinput${name}/:id`} component={component}
/>
))}
</Switch>
</Router>
</div>
);
}
I was first suggesting to create a "routes" config array that can be mapped.
const imagePostRoutes = [
{ name: "cross", component: CrossPost },
{ name: "freedom", component: FreedomPost },
{ name: "waterfall", component: WaterfallPost },
];
...
{imagePostRoutes.map(({ component, name }) => (
<Route key={name} path={`/getInput/${name}/:id`} component={component} />
))}
The second suggestion is to use a single generic dynamic route where a match parameter could specify the post type and a general post component to render the specific image post component. This is a very stripped down minimal version.
Define the route.
<Route path="/getInput/:imagePostType/:id" component={ImagePost} />
Create a Map of match param to component to render.
const postComponents = {
cross: CrossPost,
freedom: FreedomPost,
waterfall: WaterfallPost,
};
Create a component to read the match params and load the correct post component from the Map.
const ImagePost = () => {
const { id, imagePostType } = useParams();
const Component = postComponents[imagePostType];
if (!Component) {
return "Unsupported Image Post Type";
}
return <Component id={id} />;
}
The app displays all photos <Photo> in a grid <PhotoGrid>, then once clicked, a function in <Photo> changes URL with history.push, and Router renders <Single> based on URL using useParams hook.
PhotoGrid -> Photo (changes URL onClick) -> Single based on URL (useParams).
I must have messed something up, becouse useParams returns undefined.
Thanks for all ideas in advanced.
App.js
class App extends Component {
render() {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
</>
)
}
}
export default App;
Photogrid.js
export default function PhotoGrid() {
const posts = useSelector(selectPosts);
return (
<div>
hi
{/* {console.log(posts)} */}
{posts.map((post, i) => <Photo key={i} i={i} post={post} />)}
</div>
)
}
in Photo I change URL with history.push
const selectPost = () => {
(...)
history.push(`/view/${post.code}`);
};
Single.js
import { useParams } from "react-router-dom";
export default function Single() {
let { id } = useParams();
console.log("id:", id) //returns undefined
return (
<div className="single-photo">
the id is: {id} //renders nothing
</div>
)
}
When using useParams, you have to match the destructure let { postId } = useParams(); to your path "/view/:postId".
Working Single.js
import { useParams } from "react-router-dom";
export default function Single() {
const { postId } = useParams();
console.log("this.context:", postId )
return (
<div className="single-photo">
{/* render something based on postId */}
</div>
)
}
You should use the same destructure as mentioned in your Route path. In this case, you should have written :
let { postID } = useParams();
I will mention two more mistakes which someone could make and face the same problem:
You might use Router component in place of Route component.
You might forget to mention the parameter in the path attribute of the Route component, while you would have mentioned it in the Link to component.
Ensure the component where you call useParams() is really a child from <Route>
Beware of ReactDOM.createPortal
const App = () => {
return (
<>
<Switch>
<Route exact path="/" component={PhotoGrid}/>
<Route path="/view/:postId" component={Single}/>
</Switch>
<ComponentCreateWithPortal /> // Impossible to call it there
</>
)
}
You have to check API that you are using. Sometimes it's called not just id. That's why useParams() do not see it
I have a very basic app and I want to read the request parameter values
http://localhost:3000/submission?issueId=1410&score=3
Page:
const Submission = () => {
console.log(this.props.location); // error
return ();
}
export default Submission;
App
const App = () => (
<Router>
<div className='App'>
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/submission" component={Submission} />
<Route path="/test" component={Test} />
</Switch>
</div>
</Router>
);
export default App;
Did you setup correctly react-router-dom with the HOC in your Submission component ?
Example :
import { withRouter } from 'react-router-dom'
const Submission = ({ history, location }) => (
<button
type='button'
onClick={() => { history.push('/new-location') }}
>
Click Me!
</button>
)
export default withRouter(Submission)
If you already did that you can access the params like that :
const queryString = require('query-string');
const parsed = queryString.parse(props.location.search);
You can also use new URLSearchParams if you want something native and it works for your needs
const params = new URLSearchParams(props.location.search);
const foo = params.get('foo'); // bar
Be careful, i noticed that you have a functional component and you try to access the props with this.props. It's only for class component.
When you use a functional component you will need the props as a parameter of the function declaration. Then the props should be used within this function without this.
const Submission = (props) => {
console.log(props.location);
return (
<div />
);
};
The location API provides a search property that allows to get the query parameters of a URL. This can be easily done using the URLSearchParams object. For example, if the url of your page http://localhost:3000/submission?issueId=1410&score=3 the code will look like:
const searchParams = location.search // location object provided by react-router-dom
const params = new URLSearchParams(searchParams)
const score = params.get('score') // 3
const issueId = params.get('issueId') // 1410