I am making my first web-app using react, react router v4, and redux/react-router-redux.
it is a shopping site. I have my list of my products saved in the store and can access them fine. I have produced a products list page, when clicking on the products' image i have routing setup to take me to a new url(/'productmodel').
Currently I have a 'ProductPage' component for which I have passed in props relevant to the specific product, for each corresponding route, within my router. This seems like a very long way of doing things.
What I would like to do is render for each of the routes and then have the ProductPage component itself, render the right product depending on the route (URL address).
What is the best way to do this??
Thank you in advance :)
Are you after something like this?
ProductsListPage:
....
render() {
<div>
{this.props.products.map(product =>
<Link key={product.id} to={"/product/"+product.id}>
{product.name}
</Link>
)}
</div>
}
....
ProductPage:
....
componentWillMount() {
this.props.actions.getProduct(this.props.match.params.productid);
}
....
render() {
<div>
<span>{this.props.product.name}</Link>
</div>
}
....
Your route for product page would look like this:
<Route exact path="product/:productid" component={ProductPage} />
So what's happening here is when you click on a product on Products List page, you get redirected to the product page that has the product id as a parameter. On component mount on product page, you retrieve the product passing the productid from params (url).
ok so I manage to do it!
I created a ProductPageContainer inorder to pass my products list from the store:
function mapStateToProps(state) {
let products = state.kitsProducts;
return {
products: products
};
};
I then, within my component, got the productid from the params; as suggested by Hossein:
createProductPageComponent() {
let activeProduct = this.props.products.filter(product => product.id === this.props.match.params.productid)
return activeProduct.map(product =>{
return (
<ProductPage
key={product.id}
brand={product.brand}
productName={product.model.toUpperCase()}
price={"£"+product.price}
productImage={product.image}
text={product.text}
/>
)
})
}
and now my product page renders to the the right route, depending on which product was selected in the previous product list page. And the right product, depending on what route it has been rendered in.
Thanks Hossein!!! You helped me more than you may think!
Related
I have a page where i add products to a product list. After a successful add, the app needs to go back to the list to see the products again with the new entry added. The redirecting to the product list works fine, and i see the products again, but the new product is not on the list. In order to view the new added products, i have to hard reload the page. For some reason the component is not getting re-rendered thus not requesting the new product list from the server.
Why is this happening? How can I make the ProductPage re-render without forcing a full app refresh.
I have reproduced the problem in this codesandbox.
This was pretty much covered by Jacob Smith in his above comment. However, to expand a little upon his explanation, you have a setup that looks something like this:
// App.jsx
return (
<>
<h1>Some header</h1>
<Switch>
<Route path='productlist' component={ProductList} />
</Switch>
</>
)
// ProductList.jsx
const [state, setState] = usetState();
useEffect(() => {
loadSomeData().then(data => {
setState(data);
})
}, []);
return (
<>
<Switch>
<Route path='productlist' render={() => <ProductPage data={state} />} />
<Route path='productlist/add' component={AddProduct} />
</Switch>
</>
)
Due to this data flow, new data is only loaded when ProductList mounts, unmounts, and mounts again. However, nothing you do in either ProductPage or AddProduct ever unmounts ProductList, since both are rendered in that component.
This can be fixed in two main ways, move the render of AddProduct to the top level in App.jsx or, if Axios.post returns the new data, you can hand down the state setting method to AddProduct and set the state in the .then callback, since the parent component is not unmounted.
Another way is that you should really hard reload after the submitting the form by adding the following code at the end of the form submit handler function of your form.
window.location = '/productList';
I'm trying to create a webshop with React as front-end framework and have gotten stuck on the routing of the products. I currently have all the products in a json table and import these into my webshop which works fine when the product page is accessed through the link but whenever I go to the product page directly I get an error stating that my props are undefined. I have
<Router>
<Switch>
<Route exact path="/products/:productId" component={Product} />
</Switch>
</Router>
as my router to the product page and link to the page using:
<Link to ={{pathname:`/products/` + product.url, ProductdetailProps:{title: product.title, description: product.text, image: product.image}}}>
The Product page looks like this:
export const Product = ({location}) => {
return (
<div>
<img src={location.state.image} alt={backupImage}/>
<p>{location.state.title}</p>
<p>{location.state.description}</p>
</div>
);
};
Should I keep on "creating" the product pages this way and if so can someone help me with this or should make each product page seperately and link those?
I have a route with a url that changes based on the id of the object I clicked to access the route.
//Route
<Route path="InColl/:id" component={InColl}/>
//InColl Component
import React from 'react'
import './css/InColl.css';
export default function InColl({match}) {
const userId = match.params.id;
return (
<div className="margin">
<div className="inv">
Hello World!!!
</div>
</div>
)
}
The :id changes via the link component underneath. I mapped an array of objects each with their own unique id. Depending on which object I click, I will be taken to a new page with the url looking like "
http://localhost:3000/InColl/(The Id of The Object)".
<Link to={"InColl/" + this.props.c._id} >
For some reason the InColl page with the unique id doesn't render anything. I'm not sure what the issue is and I would appreciate some help. Thank you.
/The routes need to be a relative path
change route to
<Route path="/InColl/:id" component={InColl}/>
and link to
<Link to={"/InColl/" + this.props.c._id} >
I'm using gatsby js and trying to figure out how to have a page level side bar with Gatsby links that render a new component inside a div in the same page I can do this using react-router-dom but in Gatsby all I can find is how to create blog posts which is driving me nuts as every tutorial I find is the same blog post.
Here is my layout page /layouts/index.js
export default ({ children }) => (
<div id="layout">
<header>
<h3>Header</h3>
<MainNav />
</header>
{children()}
</div>
)
About Page
/pages/about.js
export default ({ location, match }) => {
console.log('location = ', location, 'match = ', match );
return (
<div id="about">
<SideBar />
<div id="content">
// . add child template or component for link clicked in sidebar
</div>
</div>
);
};
What I'm trying to do is when a user clicks on a link in the side bar stay on about but render a new component or template based on the gatsby-link clicked in the about sidebar.
The About SideBar component
/components/about/side-bar.js
const SideBar = () => {
return (
<div id="side-bar">
{/* <li><Link to='/about?sort=name'>work</Link></li> */}
<li><Link to="/about/work">work</Link></li>
<li><Link to='/about/hobbies'>hobbies</Link></li>
<li><Link to='/about/buildings'>buildings</Link></li>
</div>
)
}
Problem with the links above, they are trying to go to a new page called.
/about/work
This is not what I'm trying to do. Again I'm trying to make it stay on about but render a new component inside the content div.
Please help gatsby is so all over the place as far as docs goes. ok maybe its just me and not getting the docs clearly.
Thanks
UPDATE:
I tried adding a page suing createPage which works for me kind of but it doesn't pass the match.params id
gatsby-node.js
exports.createPages = ({ boundActionCreators }) => {
const { createPage } = boundActionCreators;
const myComponent = path.resolve('src/pages/about/index.js');
createPage({
path: '/about/:id',
component: myComponent
})
}
After a long time of trying to understand Gatsby and I can say I still don't as its docs are vast and not very clear. But once I started to look at the node-apis and onCreatePage it gave me some ideas. This is what the docs literally say.
onCreatePage
Called when a new page is created. This extension API is
useful for programmatically manipulating pages created by other
plugins e.g. if you want paths without trailing slashes.
So the only part in here that gives me a hint of this might be the key to helping me is this line. useful for programmatically manipulating pages created by other
plugins
Anyway this got me writing some code at least. Then about 3 hours later I found a plugin that was doing exactly what I was trying to do with this method. The plugin is called gatsby-plugin-create-client-paths key here is client-paths!!!!!
This makes life worth living! So in my case above I just wanted to be able to use Gatsby's router ( which is just react-router-dom behind the scenes), to pass me and id or value to routers match.params object. It still doesn't but what it does do is checks for any path after a prefix like /folder/ in my case '/about/work and recreate the page with a template component (in my case keep using pages/about/index.js), which is my template. Now that we have about/index.js rendering for ever link after /about/ then we can use some internal switch statement to handle the location that is been passed to /about/index.js. Still don't get match.params update but I do get props.location.pathname; which allows me to extract everything after the prefix to use in a switch statement to render my specific components based on the routes pathname. Enough rabbiting on here is a rough solution to show as an example.
So add the plugin as an npm install.
open up gatsby.config.js and add the below code to the exports.
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/about/*`] },
},
]
}
Then in my main about page pages/about/index
import React from "react";
import SideBar from '../../components/about/side-nav';
export default (props) => {
const { pathname } = props.location;
var n = pathname.lastIndexOf('/');
var pageId = pathname.substring(n + 1);
const page = () => {
switch(pageId){
case '':
return (
<div>Work Page</div>
);
case 'work':
return (
<div>Work Page</div>
);
case 'hobbies':
return (
<div>Hobbies Page</div>
);
case 'buildings':
return (
<div>buildings Page</div>
);
}
}
return (
<div id="about">
<SideBar />
<div id="content">
{page()}
</div>
</div>
);
};
Then in my sidebar I call it like this.
<li><Link to="/about/work">work</Link></li>
<li><Link to='/about/hobbies'>hobbies</Link></li>
<li><Link to='/about/buildings'>buildings</Link></li>
Hopefully this will help someone else out. After all this I'm starting to really question the bulk of gatsby especially with docs not been very clear. Based on the response to my question I guess not many people in stackoverflow's community are using Gatsby which is worrying when you need help. It does look like Gatsby's github community is very helpful but that should be for bug issues and not for questions like mine, but encouraging to see.
Hope this helps someone.
I am trying to create a job site. Following pages shows list of all the jobs which is shown once user hits search button from home page. So basically this is the second page.
In this page i am catching all the search parameter from url and fetching data from api and result is shown as below:
Once the user clicks individual joblist, detail page should load on the same page without changing header and fixed component with unique URL for the detail page. Expected result shown below:
My Problem:
I manage to create a nested Route, which renders detail page on the same page and also has a unique url. But it renders on top of existing job list. I mean if user clicks on joblist1, detail page renders on top of subsiquent list(above list: 2, 3, 4). But expected result is to only render detail page but not list of jobs when individual job list is clicked.
My code: I have only shown part of the code for brevity and simplicity.
1) jobs.js: Passes state data to child component to show list.
return(
<div>
<div>
fixed component
</div>
<div>
<RouteHandler />
<JobLists joblists={this.state.joblists} />
</div>
</div>
)
2) jobList.js: uses .map function to go through all data and handleclick function generate url and opens that url once user clicks individual link. Router catches nested route and loads value inside jobs.js in " ".
handleClick: function(i){
var base_path = window.location.protocol + '//' + window.location.host;
base_path += '/#/jobs-detail';
window.location= base_path;
},
render: function(){
var jobListRow = this.props.joblists.map(function(jobrowobj, i){
return(
<div key={jobrowobj.id} onClick={this.handleClick.bind(this, i)}>
<img src={jobrowobj.logo} alt="" />
<h3>{jobrowobj.title}</h3>
</div>
)
}.bind(this));
return(
<ul id="joblists">
{jobListRow}
</ul>
)
}
3) Route file:
var routes = (
<Route handler={App}>
<DefaultRoute handler={Home} />
<Route name="jobs" path="jobs" handler={Jobs}>
<Route name="jobs-detail" handler={JobDetail} />
</Route>
<NotFoundRoute handler={NotFoundPage} />
</Route>
);
I am not sure what is the best way to switch certain section (component) on a page as in my case switching between joblist component and jobdetail component. As you can see i am only able to load other component on top of existing component which is not the expected result.
Also would appreciate if any hint is given to maintain scroll position on the job list on user hitting back button.
I suggest you to upgrade your react-router to 1.0.0-rc1, and the API is more clear. Your problem is similar to the official introduction. The nested component will be passed as this.props.children, and you can insert it into the jobListRow.
About the scroll position, there's a github issue discussing how to restore it :)