I'm trying to learn react-router-dom (react router version 4.x).
I don't understand the behavior of a route with a parameter and I show a simple example.
I have a list of links of defined routes like this:
<li>Link to='/'>Home</Link></li>
<li>Link to='/product/1'>Product 1 details</Link></li>
<li><Link to='/product/2'>Product 2 details</Link></li>
and I have a route definition like this:
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/product/:number' component={Product}/>
</Switch>
now if run my web page and click on link "Product 1 details" I show a test "Product" component where I return "Product number 1" (composing text reading from props.match.params.number).
Then if I click on link "Product 2 details" it still show me "Product number 1"!
It seems that route doesn't change anymore if it starts with "product":
if I click on "home" link and then on "Product 2 details" then it refresh and works.
I would like to be able to force the product page refresh, is it possible?
Thanks in advance
As you can see in the snippet below it works as you wish.
Have you encapsulated the Switch component in a Router? (HashRouter or BrowserRouter) That seems to be the issue.
Even if I repeat the Home component on Product and click the same links the app behaves as it is supposed to. (btw don't repeat the Home component on the Product component it was a way to examplify stuff)
const Home = () => (
<ul>
<li><ReactRouterDOM.Link to='/'>Home</ReactRouterDOM.Link></li>
<li><ReactRouterDOM.Link to='/product/1'>Product 1 details</ReactRouterDOM.Link></li>
<li><ReactRouterDOM.Link to='/product/2'>Product 2 details</ReactRouterDOM.Link></li>
</ul>
);
const Product = props => (
<div>
<Home />
<p>{props.match.params.number}</p>
</div>
);
const App = () => (
<ReactRouterDOM.HashRouter>
<ReactRouterDOM.Switch>
<ReactRouterDOM.Route exact path='/' component={Home}/>
<ReactRouterDOM.Route path='/product/:number' component={Product}/>
</ReactRouterDOM.Switch>
</ReactRouterDOM.HashRouter>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/react-router/umd/react-router.min.js"></script>
<script src="https://unpkg.com/react-router-dom/umd/react-router-dom.min.js"></script>
<div id="root"></div>
Without seeing your Product component this is difficult to answer -- if you are accessing and storing the number parameter in the constructor or componentWillMount lifecycle method, those will not execute again when the route changes since you are not loading a new component despite the "new" route. React is simply updating the props being sent to the current instance of the Product component.
Try rendering the Product number X directly in your Product component render function, like this:
<span>Product number { this.props.match.params.number }</span>
If you would like to store this in your component state or as an instance property, look for it to update in one of the other lifecycle methods like componentWillReceiveProps.
product component here:
import * as React from 'react';
export default class Product extends React.Component<any, any>
{
render() {
return (
<div>
Product number { this.props.match.params.number}
</div>
);
}
}
Related
I am building a gallery where you click on the image and it will load in a separate component using props, this image is a URL, taken from an array, where the src property is loaded as a background image via CSS. My challenge is connecting the src data to the child component. See original question
I have found a solution to pass the data using the Link component. Now the URL string is being read like this: http://localhost:3000/https://photos.smugmug.com/photos....
As you can see there is an address within the address in the string.
I have tried changing the state of the URL string but did not work.
My question, how do I write a redirect to fix the HTTP address removing the localhost address
UPDATE
Many thanks to Taylor, Drew, and Ajeet for all of your help!
The solution is posted below, the main issue was I needed a function in the Image component to connect the src props from the GalleryContainer component.
I also changed all "a tags" to "Link components" to keep consistency. More details are in the explained solutions from Drew and Taylor, and also Ajeet code box here
Issues
I don't know why but you don't seem to use Link components consistently in your app; when using anchor (<a>) tags these types of links will reload the page and your app. A similar issue occurs when you manually set the window.location.href.
The Image wasn't correctly accessing the passed route state.
Solution
App
Reorder your routes from more specific to least specific, and remove the link from within the Switch component, only Route and Redirect components are valid children.
function App(props) {
return (
<>
<Router>
<Switch>
<Route path="/gallery" component={GalleryList} />
<Route path="/image" component={Image} />
<Route path="/" component={Home} />
</Switch>
</Router>
</>
);
}
Home
Use Link component to enter the gallery.
import { Link } from "react-router-dom";
...
<Link to="/gallery">
<h4>Click Here to Enter Gallery!</h4>
</Link>
GallerayList
Use Link component for the link back home.
import { Link } from "react-router-dom";
...
<Link to="/">Home</Link>
GalleryContainer
Refer to image source consistently, i.e. src. Pass along also the image id in route state, using a Link.
const GalleryConatiner = (props) => {
return (
// generates the gallery list!
<ul>
<li className={styles["gallery-list"]}>
<Link
to={{ pathname: "/image", state: { id: props.id, src: props.src } }}
>
<div
className={styles["div-gallery"]}
style={{
backgroundImage: `url(${props.src})`,
height: 250,
backgroundSize: "cover"
}}
></div>
</Link>
</li>
</ul>
);
};
src/Public/Image
Use a Link for the link back to the gallery. Use the useLocation hook to access the passed route state.
import { Link, useLocation } from 'react-router-dom';
const Image = (props) => {
const { state: { id, src } = {} } = useLocation();
return (
<section>
<h1 className={styles["h1-wrapper"]}>Image :{id}</h1>
<div className={styles.wrapper}>
<Link to="/gallery">BACK TO GALLERY</Link>
<ImageContainer id={id} key={id} src={src} />
</div>
</section>
);
};
src/Public/ImageContainer
It isn't clear what your plan is for this component and clicking on the div rendering the passed image as a background so just remove the window.location.href logic with history.push if you want to navigate elsewhere. You can access the history object via the useHistory React hook.
Demo
The disconnect is between the GalleryContainer and Image components. In order to access data from the <Link to=...> within the next component, you need to use props.location.propertyName.
So for example, your GalleryContainer needs to link like this:
<Link to={{ pathname: "/image", src: props.src }}>
And then the value can be retrieved inside the Image component like so:
<ImageContainer id={props.id} key={props.id} src={props.location.src} />
You can use
<Link to={{ pathname: "/image", state: { url: props.src } }}>
but then you would have to access it in the linked component like this: props.location.state.url
From there, you can use an <a> tag with an href to link to the src property.
You can simply use a tag to redirect.
<a target='_blank' href={}>
Link
</a>
Remove target attribute if you dont need to open in new tab.
I want to render a table in React on a page. Table has contains a lot of rows so I want to apply pagination. Like when someone clicks on page 2 link(/open_alerts/?page=2), table fetches next rows while keeping the same page and just changing the data. But i am facing problem, i can not know in my AlertsTable component the value of page parameter. i saw something useParams hook but this is not allowed in class component. How can I achieve the desired thing?
Home page is at / and it contains link to /open_alerts/ page.
App.js:
<Router>
<Switch>
<Route exact path="/">
<Link to="/open_alerts/">Open Alerts</Link>
<div className="Charts">
<AlertsChart id="alerts-chart"/>
<RegionsChart id="regions-chart"/>
</div>
</Route>
<Route path="/open_alerts/">
<Link to="/">Home page</Link>
<AlertsTable />
</Route>
</Switch>
</Router>
How do i know in AlertsTable component the page number so that i may use page number in my API call to server to get the paginated response?
alerts_table.js:
export class AlertsTable extends React.Component {
.
. // code here
.
render() {
return (
<div>
<h1>
Open alerts
</h1>
{ !this.state.isfetched ? <p>Fetching open alerts</p>:
<div>
<AlertsRenderer alerts={this.state.alerts}/>
<PaginatedButton pages={this.state.pages_count} />
</div> }
</div>
);
}
}
paginated_button.js:
export function PaginatedButton(props) {
return (
<Router>
{props.pages.map(page_no => (
<button>
<Link exact to={"/open_alerts/?page="+(parseInt(page_no)+1)}>{page_no+1}</Link>
</button>
))}
</Router>
)}
AlertsRenderer is just a functional component that gets alerts from props and displays using HTML table tags.
useParams wouldn't help you anyway since that is the route's match params, nothing to do with an URL's query string parameters.
You will have to process the query string, which can be accessed from the location route prop.
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
state: {
[userDefined]: true
}
}
Use URLSearchParams to process the query string.
const search = this.props.location.search;
const queryParams = Object.fromEntries(new URLSearchParams(search));
const search = "?page=2";
const queryParams = Object.fromEntries(new URLSearchParams(search));
console.log(queryParams);
You can access the route props by a number of ways, but with class-based components they are either directly passed when the component is directly rendered by a Route component on the render or component props, or if you decorate the component with the withRouter Higher Order Component.
In your case it seems that <AlertsTable /> is not directly rendered by a Route component so you should decorate AlertsTable with withRouter and access the location prop that is passed.
I have a problem with the code - I do not understand how to solve it - I tried for a few hours - it does not work.
I do not know what's going on. I'm just trying to access from a component of courses - and a component of a single course.
I get this error:
This is the code I wrote down so far - not a long code.
course.js:
import React, { Component } from 'react';
class Course extends Component {
render() {
return (
<div>
<h1>this.props.location.title</h1>
<p>You selected the Course with ID: {this.props.match.params.id}</p>
</div>
);
}
}
export default Course;
courses.js:
import React, { Component } from 'react';
import './Courses.css';
import { Link } from "react-router-dom";
class Courses extends Component {
state = {
courses: [
{ id: 1, title: 'Angular - The Complete Guide' },
{ id: 2, title: 'Vue - The Complete Guide' },
{ id: 3, title: 'PWA - The Complete Guide' }
]
}
render() {
return (
<div>
<h1>Amazing Udemy Courses</h1>
<section className="Courses">
{
this.state.courses.map(course => {
return (
<Link
key={course.id}
to={{
pathname: this.props.match.url + "/" + course.id,
title: course.title
}}>
<article className="Course">{course.title}</article>
</Link>
)
})
}
</section>
</div>
);
}
}
export default Courses;
app.js:
import React, { Component } from 'react';
import Courses from './containers/Courses/Courses';
import Users from './containers/Users/Users';
import { NavLink, BrowserRouter, Switch, Route } from "react-router-dom";
import './App.css';
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<header>
<nav>
<ul>
<li>
<NavLink
to="/users"
exact
activeClassName="my-active"
activeStyle={{
color: '#fa923f',
textDecoration: 'underline'
}}>Users
</NavLink>
</li>
<li>
<NavLink to={{
pathname: '/courses',
hash: '#submit',
search: '?quick-submit=true'
}}>Courses
</NavLink>
</li>
</ul>
</nav>
</header>
<Switch>
<Route exact path="/">
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/courses">
<Courses />
</Route>
</Switch>
<ol style={{ textAlign: 'left' }}>
<li>Pass the course ID to the "Course" page and output it there</li>
<li>Pass the course title to the "Course" page - pass it as a param or score bonus points by passing it as query params (you need to manually parse them though!)</li>
<li>Load the "Course" component as a nested component of "Courses"</li>
<li>Add a 404 error page and render it for any unknown routes</li>
<li>Redirect requests to /all-courses to /courses (=> Your "Courses" page)</li>
</ol>
</div>
</BrowserRouter>
);
}
}
export default App;
You're trying to access the props that come from react router, with the syntax that you're using on your app.js file you're not passing any props. To be able to pass react router props to your component you can use this syntax instead:
<Route path="/courses" component={Courses} />
So everytime you access a link that takes you to the courses you will have available in you component all the props that are coming from react router.
Here's a sandbox with an example of a component that uses that syntax to obtain all the router props vs another that uses the syntax you're using and it does not have access to the router props:
Sandbox with example
I'm using functional components but with your example doing this.props will give the router props including the match object. With your courses component being a class I think this is the easiest way to do it, you can also wrap the component in a withRouter HOC to accomplish the same thing:
WIth router docs
You need to set props in your child components for them to be available, like so...
<Courses
match={this.props.match}
/>
Right now, all you have is <Courses />, without passing an props. So in the Courses class, of course you'll get this message: Cannot read property 'url' of undefined on your call of <Link ....this.props.match.....>.
But if you pass along the match prop, like match={this.props.match}, there should no longer be this error.
Take a look at the ReactJS documentation on how props work...
However, elements can also represent user-defined components:
const element = <Welcome name="Sara" />;
When React sees an element representing a user-defined component, it passes JSX attributes and children to this component as a single object. We call this object “props”.
Source: ReactJS.org: Components and Props
I am having fun implementing routing in my app, but find it hard to set a page title in the parent with the name of the screen that Route is rendering, like this:
class App...
setTitle(title) {
this.state.screentitle = title
this.setState(this.state)
}
...
<h1>{this.state.title}</h1}
...
<Route path='/Search' render={props => <ScreenSearch setTitle={this.setTitle} {...props} />} />
and each child holds the actual title text, like:
class ScreenSearch...
componentDidMount() {
this.props.setTitle("Search");
}
While this actually works, I would prefer to keep the title texts for all child screens in the parent, together with all Route rules. After all, the child objects should do just their job, like implementing a search page, but have no need to know what it is called at the parent level.
Also, this seems a complex way with too much code to just set a stupid title.
As a beginner with React Route, I would like to ask if there is a better way.
You can implement a Layout component that will have shared items on it like page title,navigations etc.
Layout Component
class DefaultLayout extends React.Component{
render() {
return (
<div>
<div className="header">
<h1>Company Name</h1>
<Navigation />
</dv>
<div className="content">
<h1>{this.props.title}</h1>
{this.props.children}
</div>
<div className="footer">
<p>Copyright (c) 2014 Company Name</p>
</div>
</div>
);
}
};
module.exports = DefaultLayout;
and the child page
var React = require('react');
var DefaultLayout = require('./DefaultLayout');
class PageOne extends React.Component{
this.state={
title:"Page 1"
}
render() {
return (
<DefaultLayout title={this.props.title}>
<p>The page's content...</p>
<p>goes here.</p>
</DefaultLayout>
);
}
}
This is a way to implement it, if you have many routes you can create your own Route component where you give the title as a prop
<Route path='/Search' render={props => {
this.state.screentitle!==this.setTitle && this.setTitle(this.setTitle)
return <ScreenSearch {...props} />}
/>
After seeing your comment I understand you want to remove the set Title so you will need the location from react-router and an object something like this:
let routeTitles = {
/'search': 'The search' ,
'/jobs': 'Look for a job'
}
<Title>{routeTitles[this.props.location.pathname]}</Title>
I am using:
React, Redux, React-Router, Meteor
Following situation:
I have a modal with two tabs, based on the chosen tab a different list of selections will be shown in the content area, picking a selection will then give a list of subselection (all within the modal).
Desired behavior:
I want the user to be able to use the browser's back button to "undo" clicking on one of the tabs or picking a selection.
What I tried so far with no success:
use withRouter HOC to wrap my component, so that I would get access to history. I would then use history.push(currentUrl,{selectedTab:selection})
-> Problem: the state from push wasn't stored on this histories location object, but only in the state of the history object associated with my rootcomponent (way further up the tree)
(More promising so far) Simply import the history I created with createHistory in another module and put that into component state on construction.
I would then access the push method by using this.state.history.push(currentUrl,{selectedTab:selection}), which works fine and I can find the pushed state under this.state.history.location.state.selectedTab. However, using the browser's back button does not cause a rerender and therefore the selection will stay the same.
Any ideas?
Cheers
Another approach you could take would be to use query params with React router. And in your component change the tabs based on the query param. Since the url changed you should get the default behaviour your looking for.
I have a project with almost exactly that situation. The difference, is that instead of it being in a modal, I have it in my header. Here is what I did:
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
class Header extends Component {
render() {
return (
<ul className="tabs tabs-fixed-width z-depth-1">
<li className="tab">
<Link id="books-tab" to={'/books'}>Books</Link>
</li>
<li className="tab">
<Link id="stats-tab" to={'/stats'}>Stats</Link>
</li>
{Meteor.userId() &&
<li className="tab">
<Link id="user-tab" to={'/user'}>Account Settings</Link>
</li>}
</ul>
);
}
}
export default withRouter(Header);
The react-router-dom <Link>... being the operative item that allows this to happen.
Then, when I click on the tab, each link goes to the specified url and keeps it in history so I can go back and get to the previous tab.
I refined the second approach and it is working now. It does still feel hacky to import the history into my module (as a local variable), and thus bypassing react tree, redux store and react router altogether.
However, it is working and not causing any bugs so far. So here we go ...
I instantiate my history object in: store.js module:
import createHistory from 'history/createBrowserHistory';
...
const history = createHistory();
...
export { history , store }
that as you can see exports history. I then feed said history to my Router (ConnectedRouter in my case, as I am using react-router-redux) in: routes.js module
import { store , history } from '/imports/client/store';
const MyStoreRouter = () => {
return (
<Provider store={store}>
<ConnectedRouter history={ history }>
<div>
<Switch>
<Route exact path="/" component={Landing}/>
<Route path="/" component={MainApp}/>
</Switch>
</div>
</ConnectedRouter>
</Provider>
);
};
Now for the wacky part. I import the history into a display component and use it to set up a listener (to state changes in history.location) and change the state accordingly:
DisplayComponent.js module:
import { history } from '/imports/client/store';
....
export default class EventCreationModal extends Component {
constructor() {
super();
this.handleSelect = this.handleSelect.bind(this);
this.state = {
selectedCategory: (history.location.state && history.location.state.category) ? history.location.state.category : 'sports',
};
history.listen((location,action) => {
this.setState({
selectedCategory: (history.location.state && history.location.state.category) ? history.location.state.category : 'sports',
})
});
}
handleSelect(key) {
history.push("/Home",{'category':key})
}
render () {
return (
<Tabs activeKey={this.state.selectedCategory} onSelect={this.handleSelect} id="SportOrSocialSelector">
<Tab eventKey={'sports} .../>
<Tab eventKey={'social} .../>
</Tabs>
)
}