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.
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 am currently trying to pass some information through React-Router-DOM's Link component to another one of my components in which I tried this:
export default function Test() {
return(
<div>
<Link to={{ pathname: './NewPage', state: { testValue: true }}}>New Page</Link>
</div>
)
}
export default function NewPage(props) {
console.log(props.location.state)
return(
<div>
{props.location.state}
</div>
)
}
The error says: Cannot read property 'state' of undefined at the console.log(props.location.state) line
Note: I am using React Hooks not class
The route props that react-router allows you to use must be passed into your component somehow. There are several patterns for accomplishing this, a HOC (withRouter), a hook, or by having Route pass them in directly by passing it a component prop.
Notice how you instantiate the NextPage component
<Route exact path='/NewPage' component={() => <NewPage />}/>
It is given no props.
The component prop is intended to be passed a reference to a component. You have provided an anonymous function in order to satisfy this requirement, but you lose the react-router props because of it. In order to get them you would need to pass them along (props) => <NewPage {...props}/> (But please don't do that, use render if you really want to use a function).
Simply cut out the arrow function and give the component prop the component reference, and it will get the rout props it needs:
<Route exact path='/NewPage' component={NewPage}/>
Since you're using functional components, I would recommend rendering your components as Route children, and using the hooks to access the react-router values.
<Route exact path='/NewPage'>
<NewPage />
</Route>
....
export default function NewPage(props) {
let location = useLocation();
return (
<div>
{location.state}
</div>
)
}
This syntax allows you to easily add other props that you may need to NewPage, while still being able to use the route props through hooks.
I'd suggest using search or hash rather than state as i'm guessing you want your data to persist; https://reactrouter.com/web/api/Link
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
}}
/>
and then you can access it just the way you did in using props.location.search or props.location.hash
you can refer to the docs to confirm the properties https://reactrouter.com/web/api/location
On the Calculation form, the result can display but what I want is when the user clicks on the button "Calculate saving range", it will link to another page and display the result as well. I try to send data from Calculate.js to Result.js but it does not work
My code is here: https://codesandbox.io/s/calculation-form-uxip8?file=/src/components/Calculator.jsx
On Result.jsx
import React, {Component} from 'react';
import {NavLink } from "react-router-dom";
class Result extends Component {
render(){
return(
<div className="result">
<h2>You could save between</h2>
<h1>{this.props.value}</h1>
<NavLink to="/">Use Calculator Again</NavLink>
</div>
)
}
}
export default Result
Anyone can stop by and give me some help. Thank you so much.
You can simply pass the data along with the route push/replace.
Using the Redirect component with existing implementation
<Redirect
to={{
pathname: "/result",
state: {
value:
"$" +
Math.floor(1.69 * this.state.square * (10 / 100)) +
"- $" +
Math.floor(1.69 * this.state.square * (30 / 100))
}
}}
/>
Since your components are being directly rendered by a Route component they are passed route props. This means that Calculator has access to the history prop to imperatively redirect, and Result can access the location prop to get the route state.
Calculator.js
Update the submit handler to do the redirect to the "/result" route and pass along the state. Remove the div at the end of the return with Result, you don't need that on this page. Use history.push if you want the user to be able to go back to form.
handleSubmit = (event) => {
event.preventDefault();
this.props.history.replace({
pathname: "/result",
state: {
value:
"Your saving range is: $" +
Math.floor(1.69 * this.state.square * (10 / 100)) +
"- $" +
Math.floor(1.69 * this.state.square * (30 / 100))
}
});
};
Result.js
Just need to access the route state props.location.state.value.
class Result extends Component {
render() {
return (
<div className="result">
<h2>You could save between</h2>
<h1>{this.props.location.state.value}</h1> // <-- route state
<NavLink to="/">Use Calculator Again</NavLink>
</div>
);
}
}
Important
Make sure your routes and app code are wrapped in a router! Swap the order of your routes so you specify more specific routes before less specific routes. This is so the Switch can try matching more specific routes first, then falling back to the less specific ones.
...
import { Switch, Route, BrowserRouter as Router } from "react-router-dom";
...
function App() {
return (
<div className="App">
<Router> // <-- need to wrap app in Router so routes work correctly
<Switch>
<Route path="/result" component={Result} />
<Route path="/" component={Calculator} />
</Switch>
</Router>
</div>
);
}
In React, when you want two components to communicate together, there must be a state in one of their common ancestors.
In your app, the common ancestor of Calculator and Result is App so you should have a state in App.
function App() {
const [result,setResult] = useState(null)
return (
<div className="App">
<Switch>
<Route exact path="/" component={() => <Calculator setResult={setResult}/>} />
<Route path="/result" component={() => <Result result={result}/>} />
</Switch>
</div>
);
}
Then, Calculator can call setResult internally to modify result which can then be read by Result.
Another option is to use Redux.
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>
);
}
}
For the use case when a page has an internal route, the prompt triggers even when navigating to an internal route where a prompt might not be necessary, see code example. Is there a way to disable the prompt when navigating to known safe routes?
import React, {Component} from 'react';
import {BrowserRouter, Route, Switch, Link, Prompt} from 'react-router-dom';
export default class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route component={About} path={"/about"}/>
<Route component={Home} path={"/"}/>
</Switch>
</BrowserRouter>
);
}
}
class Home extends Component {
constructor(props) {
super(props);
this.state = {input: 'hello?'}
}
render() {
return (
<div>
<h1>Home</h1>
<input value={this.state.input}
onChange={(e) => this.setState({input: e.target.value})}/><br />
<Link to={"/info"}>More Info</Link><br />
<Link to={"/about"}>About Page</Link><br />
{/*Triggers even when going to /info which is unnecessary*/}
<Prompt message="Move away?" when={this.state.input !== 'hello?'}/>
<Route path={"/info"} component={Info}/>
</div>
);
}
}
class About extends Component {
render() {
return (
<h1>About page</h1>
);
}
}
class Info extends Component {
render() {
return (
<p>Here be some more info</p>
);
}
}
In the example above, About is a different page and so should trigger when the input has changed, which it does correctly. But the /info route is an internal route for Home, so the prompt is unnecessary, the internal state of Home is preserved after navigation so nothing is lost.
The actual use case here is for a modal to be shown when the route is active, but that is mostly CSS stuff so I excluded it from the example.
I think a callback function as a message prop is what are you looking for. You can try to give a callback function to Prompt's message prop. That callback will be called with an object as an argument which will have all the information about the next route.
This is what documentation says about it:
message: func
Will be called with the next location and action the
user is attempting to navigate to. Return a string to show a prompt to
the user or true to allow the transition.
The object has a pathname attribute which is the next path by checking it you can figure out if the path is safe. Here is what I'm talking about:
<Prompt message={(params) =>
params.pathname == '/about' ? "Move away?" : true } />
And here is the pen with working code which I've created from your examples.
Hope it helps.