Fetching data from API and react-router-dom on refresh / direct link - reactjs

I'm learning redux and routing and history with react-router-dom and react-connected-router for an app. I have an api and I do my fetchData in componentDidMount():
componentDidMount() {
const { onFetchData, prodPerPage, currentPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
In my pagination component I use a link that routes to the page which works fine onClick because I re-fetch data on click where currentPage becomes the pageIndex so data is fetched for that page.
Here is my link:
<Link to={`/products/page/${item.page}`}>
<PaginationItem {...item} />
</Link>
Here is my route:
<Route exact path="/products/page/:page" component={ProductsPage} />
If I reload my page my currentPage is set to 1 from initialState.
On Back/Forward my page again loads data for page 1.
How can I set my currentPage to be whatever I navigate to? So if my link is https://localhost:3000/products/page/3 then set currentPage to 3 and fetch the correct data?
Note: If I try to load https://localhost:3000/products/page/3sddada it doesn't redirect to 404, how do I fix that too?
Thanks in advance.

We need to get access to the dynamic pageid being passed into our route and use it to query the correct page from the API. This is easy to do using react-router-dom. The library passes in a prop called match into every route that is rendered. Inside this match object is another object called params. This holds all matching params where the key is the name we specified when creating the route and the value is the actual value in the URL.
componentDidMount() {
const { page} = this.props.match.params;
this.setState({currentPage:page});
const { onFetchData, prodPerPage, filters } = this.props;
onFetchBooks(prodPerPage, currentPage, filters);
}
React-router v4 now allows you to use regexes to match params -- https://reacttraining.com/react-router/web/api/Route/path-string
<Switch>
<Route exact path="/products/page/:page(\\d+)" component={ProductsPage}/>
<Route exact path="/products/page/:page(\\w+)" component={ErrorRedirectPage}/>
</Switch>

Related

react router, how to make page refresh on url change?

I am making a movie app, where the each movie has its own dedicated page. On this page I have provided similar movies that can be clicked on and ideally would take you to its own movie page. I have a route that looks like this
<Route name="movie" path="/movie/:movieID" exact ><MoviePage /></Route>
the link to this route is in each and every movie component that I have created. The issue is that when I click on a similar movie the url changes to the url of the similar movie and I am able to access the similar movie's properties in the developer console, however the page itself does not change. But once refreshed the page's contents change to those that correspond to the url.
I cannot find a way to force refresh the page in order to get the new movie's information to be displayed.
The issue is likely that the MoviePage component needs to react to the movieID route param updating.
Given Route:
<Route name="movie" path="/movie/:movieID" exact>
<MoviePage />
</Route>
MoviePage:
Use an useEffect hook to handle any logic/data fetching/etc based on the movieID param updating.
const { movieID } = useParams();
React.useEffect(() => {
// movieID on initial render or subsequent render when updated
// logic to use movieID and resynchronize any data.
}, [movieID]);
If still using class components, then use the componentDidUpdate lifecycle method. (Assumes MoviePage wrapped in withRouter HOC to receive route props, specifically the match prop)
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.movieID !== this.props.match.params.movieID) {
// logic to use movieID and resynchronize any data.
}
}

Component path prop is matched but is not rendered using client-only route in Gatsby

I'm trying to get Reach Router in Gatsby to navigate programmatically from one of my components. The URL is updated as expected however the route is not rendered and the Gatsby static routes list are displayed.
My code
<Router>
<PageTest1 default />
<PageTest2 path="/test2"/>
<PageTest3 path="/test3"/>
</Router>
The default component is renderd but not others.
How can I get it to render components?
You need to tell Gatsby about the routes so it knows which component to use to render those pages, as documented here.
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// Only update the `/app` page.
if (page.path.match(/^\/app/)) {
// page.matchPath is a special key that's used for matching pages
// with corresponding routes only on the client.
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}

Component render() triggered after history.push but page is blank

I want the user to be redirected to the resources list after he deleted an item on its show page. I 've read a lot of SO Q&A on the topic, but I think I have a different issue as my routes and component got hit the right way after history.push
I tracked code execution through debugger till component render and still don't understand why nothing is returned
Here are my routes in my App component (wrapped this way<Router><App /></Router>) component :
<Route component={AppHeader} />
{["/articles/:id/edit", "/articles/new"].map((path, index) =>
<Route key={index} exact path={path} component{ArticleForm}/>
)}
<Route exact path="/articles/:id" component={Article}/>
{["/", "/articles"].map((path, index) =>
<Route key={index} exact path={path} component{ArticlesList}/>
)}
I use redux so Router and ArticleList are exported this way :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component))
In AppHeader component, a delete button is provided, if user is on show or edit page. When clicking on the link, following method is triggered :
class AppHeader extends Component {
...
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.props.history.push("/")
}
...
}
Then Route with ArticlesList is triggered and should render this component. Here is what happens (breakpoints all the way in render methods):
URL is updated
Router is rendered
App header is rendered
Article list is rendered
Article list re-rendered with fecth from API (state populated with list)
Article list re-rendered with componentDidUpdate
BUT page stays blank ... I am using this.props.history.push("/") in other components and it works fine (list get re-rendered and displayed). See blank page and console.logs :
If I manually reload the page, it renders normally.
What is preventing any component to be displayed (DOM is nearly empty, I only get my empty <div id="root"></div>) in this case ?
Do one thing ,to displaying Article list use filter to update list state
deleteArticle = async () => {
await ajaxHelpers.ajaxCall('DELETE',`/articles/${this.state.currentArticleID}`, {}, this.state.token)
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})
}
Change :
this.props.history.push("/")
To :
this.setState({articleList:articleList.filter((list)=> list.id!==this.state.currentArticleID)})

fetch the right data listening to the URL in react

I use react-router-dom to navigate through my components.
When I redirect to a detail component I give an item.name at the back of the URL.
const selectedBeerID = '/beerItem/' + this.props.beers.selectedBeerItem.name;
return <Redirect to={selectedBeerID}/>;
so the url will be like http://localhost:3000/beerItem/newName I would like to get the last parameter "newName" in my redirect page, so I'am able to fetch the right BeerItem from my back-end in my detail component. Can someone tell me a good way to achieve this? thanks a lot!
I think you’re looking for URL params. From the docs of react router 4, with customizations for your case:
<Route path="beerItem/:newName" component={Child} />
const Child = ({ match }) => (
<div>
<h3>{match.params.newName}</h3>
</div>
);
Please let me know if you need additional information.
If you have something like that in your Router (and you should):
<Route exact path={`/beerItem/:itemName`} component={BeerItemComponent} />
then you can access route params inside BeerItem component by
componentDidMount() {
const {match: { params: { itemName } } } = this.props
console.log(itemName) // do something with it.
}
NOTE that BeerItemComponent should be wrapped with withRoute HOC to have access to route params or use mapStateToProps.
Here is useful link from React Router docs.

Route handling a uniquely generated URL specific to a user in ReactJS and react-router-dom v4

In the app I am working on if a user forgot their password, they click on a link that brings them to a page where they enter the username. If the username is matched, it sends a uniquely generated URL to the email associated with the username. For example:
http://localhost:8000/auth/security_questions/0e51706361e7a74a550e995b415409fdab4c66f0d201c25cb4fa578959d11ffa
All of this works fine, but I am trying to figure out how to handle this on the front-end routing using React and react-router-dom v4. I made this route.
<Route exact path='/auth/security_questions/:key' component={SecurityQuestions} />
The correct component loads related to security questions, but that is not the behavior I am after. After all, it takes anything you put after security_questions/.
What it should be doing is matching :key against the database before it loads the component.
I'm not sure about a few things:
1) How to parse out the :key portion so that I can pass it as a value to verify against the database.
2) While I have a general idea of how to handle the verification, I am not sure how to tell React: "Ok, the key has been verified in the database. Finish loading the component."
I think it would in general look like:
// ./containers/security_questions.js
componentWillMount() {
this.props.verifyKey(:key);
}
And then actions:
// ./actions/authentication.index.js
export function verifyKey({ :key }) {
return function(dispatch) {
axios
.post(
`${ROOT_URL}/api/auth/security_questions/`,
{ :key }
)
.then(response => {
dispatch('Finish loading the rest of the component')
})
.catch(error => {
dispatch(authError(error.response.data.non_field_errors[0]));
});
}
}
Or maybe instead of it finishing loading the component, it should just route to a different URL that is a protected route.
You can grab the params from the path like so (https://reacttraining.com/react-router/web/api/Route):
<Route path="/user/:username" component={User}/>
const User = ({ match }) => <h1>Hello {match.params.username}!</h1>
You will want to conditionally render based upon some state set by verifyKey.
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
render() {
if (this.state.permitRender) {
return <Component>
} else {
return <LoadingComponent />
}
You can use the render method on the route to put in your verification logic and render the appropriate control. You would need to move the action to verify the key to the component which renders the route, rather than the SecurityQuestions component.
<Route exact
path='/auth/security_questions/:key'
render={(props)=>{
let finalComponent= <ComponentToRenderWhenKeyDoesNotMatch/>;
if(this.props.verifyKey(props.match.params.key)){
resultantComponent=<SecurityQuestions/>
}
return finalComponent;
}}
/>
Route params will be inside the match.params prop:
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
See https://reacttraining.com/react-router/web/example/url-params
--
Then on the success of your security questions I would set some state in your reducer and render based on that.

Resources