React router V4 - Problems with 2 diff routes on the same page - reactjs

I have the following switch on my router
<Switch>
<Route exact path={["/", "/job/:id"]} render={(props) => <Landing {...props} />} />
/* And some other routes in here */
</Switch>
Basically both path '/' and '/job/:id' are displayed on the same landing page, but '/job/:id' is more like a component that is included within that landing page.
In my requirements, the behaviour of the landing page is as follow
When entering '/' for the first time, it will make calls to fetch the data from backend, and update it on the landing as a list of job elements.
Whenever a job element is clicked, the job element will expand and it will display the URL as '/job/2838728' (which follows '/job/:id' format) but will not make any call to the backend, despite both routes sharing the same landing page.
When i collapse / close the job element it should revert back the URL to '/', but it should not make any calls anymore.
This is the code i used to update the URL
const updateCatIndex = job => {
if (job) {
const { _id } = job;
history.replace(`/job/${_id}`);
}
}
And here where my problems are..
Whenever i click any of the job element, it will not only change the URL to '/job/:id' format but also making calls to the backend again to fetch data. I just want to update the URL and not making any calls anymore.
I only want to make call on the first '/' when entering the page.. but now whenever i close the job element, reverting the URL back to '/', it will also make calls again to the backend to fetch data.
So my issues are
How to make it so that calls to the backend are only done on the first time it enters '/' and not everytime the URL changes from '/job/:id' to '/'?
How to make it so that i change the URL from '/' to '/job/:id' without refreshing or making any calls again to the backend?
Desperately need help on this, and i don't even know where to begin for this..

Related

Reactjs router - this.props.history.push is not rendering URL queries from same path

I have a component that uses history.push to navigate URLs. The problem is that one of the URL paths relies on search parameters in the URL to render parts of the component. It works fine when the user initially navigates to the url, but when they update it while inside that path it doesn't work. Heres's an example:
App.js
<Router>
<Route path="/jobs" component={Jobs} />
</Router>
The url for jobs will contain a job ID, which is used to retrieve the data from the backend - ex: /jobs?id=6583ghawo90. With that id I make a get request inside componentDidMount() of the Jobs component to populate the page. Inside the Jobs component a user can navigate to a new job, which updates the url through this.props.history.push(`/jobs?id=${newjob.id}`). The problem is, when the user navigates to the updated URL, the component doesn't call componentDidMount() therefore doesn't request the new data. I can fix this by manually calling this.componentDidMount(), but this doesn't work if the user hits the back button in their browser.
Is there anything I can do to fix this issue?
You shouldn't be using componentDidMount but componentDidUpdate:
componentDidUpdate(prevProps) {
// compare previous jobId and current jobId
// refetch data if needed
}
I would suggest you use hooks if you are in the beginning of the development process.

How to use React Router to route to another url AND re-render page with NEW component?

In App.js, I have a button that if you click, should redirect users using React-Route to another URL, /landingpagehahaha, and should render a component called LandingPage. However, neither the URL is being changed in my browser nor the correct component being rendered. The behavior right now when you click the button is that the current page gets re-rendered, not the correct LandingPage component.
The React-Route logic is placed in a function called routeChange(). I put 2 alert() statements in it which get called, telling me that it is getting inside that function. However, nothing else changes.
I have tried using this.props.history.push("./LandingPage"); in routeChange() but it doesn't get past that statement. It appears like it behaves like response.json(), which returns from the function after it runs.
I have also tried using withRouter(), but I get a weird error that I can't call Route inside Router. I was unable to resolve that issue.
// Changes route
routeChange() {
alert("HELLO BEFORE");
alert("HELLo");
return (
<div>
<Route path="/landingpagehahaha" component={LandingPage} />;
</div>
);
}
// The button that is supposed to bring user to next page
<button onClick={this.routeChange}>Go To Next Page</button>
You need to return the Redirect component from your render function, or as use the history api to push the route into your navigation stack.
The first thing you should do, is move out the route declaration, and play it higher up in your component hierachy, you have to make sure the route declaration is rendered, when you're trying to go to the route.
Instead of using the history api, you could also use the Redirect component provided by react-router. I've made a small example here.
https://codesandbox.io/s/bold-paper-kxcri

Show/Hide route components in react

I am working on a React application which has routes like so:
<Switch>
<Route path="/edituser/:username" component={EditUser}/>
<Route path="/createuser/:type" component={EditUser}/>
<Route path="/listusers" component={ListUsers}/>
</Switch>
ListUsers component shows a table with pagination where each component in the table has a link which points to /edituser/:username.
I can edit users by clicking on the item in the table but as expected with react, once I go back to listusers/ the component is loaded again and I will be on the first page of users. I want to be on the page from where I accessed the user in the first place.
What is the best pattern to achieve this? I thought about passing in the page number to /edituser and then back to /listuser but then again I have to load all the paginated results again. Is local storage the only option? Any pointers are much appreciated.
There are two solutions for your problem:
1) Pass last active page ad route parameter and set your pagination accordingly.
<Switch>
<Route path="/edituser/:username" component={EditUser}/>
<Route path="/createuser/:type" component={EditUser}/>
<Route path="/listusers/:pageNumber" component={ListUsers}/>
</Switch>
on your componentDidMount you can use it to set state. For example :
const pageNumber = this.props.match.params.pageNumber;
2) Pass state prop in your routing. For example :
<Link
to={{
pathname: '/listusers',
state: { pageNumber: 1 }
}}/>
on your componentDidMount you can use it to set state. For example :
const pageNumber = this.props.location.state.pageNumber;
You could add the page number to the URL in the /listusers endpoint. Maybe something like /listusers/2 or /listusers?page=2 this way, when you hit the browser's back button, you're directly there. One last thing you could do but I wouldn't advise in this case is to store the page number in the history state.
As a rule of thumb, in order to get back, prefer using the history than using local storage.
If you really want to keep the data in memory, you can always use a store that is in a higher component (the root component for example) and keep the previous query over there. However you'll need to be careful about a lot of routing issues in such cases:
cache invalidation: the data changed on the server side in the mean time
user somehow gets back to a page with another table page number
loaded user presses the browser back button

React Router 4 - How to append path to existing query

I am writing an webapp that only loads when you are on a url with a query, for example:
http://localhost/site/?runapp=1
And I want to use React Router 4 in the app, but when I add a Route with a path, such as:
<Route path="/somepage" component={somePage} />
And the user clicks a:
<Link to="somepage">
to load that Route the url gets converted to:
http://localhost/site/somepage
And on refresh, the app stops working as the query is gone. How can I make React Router 4 append the paths to the needed query? I know it doesn't look pretty, but essentially I need it to route like to this url:
http://localhost/site/?runapp=1/somepage
That would make it all work, I think.
Is there a way to achieve this?
EDIT: I realized that I can simply add the ?runapp=1 in every Link to get the url to be "correct", i.e:
<Link to="?runapp=1/somepage">
but it seems hacky and doesn't allow the Route to load.

Automatic Deep Sub-Route Redirecting in React-Router

I have recently been transitioning a project from AngularJS + UI-Router+ UI-Router-Extras to React + React-Router.
One of the better features in UI-Router-Extras that I'd like to bring to React-Router is called
Deep State Redirect. In this feature, when navigating to a route which has subroutes, the application knows to redirect the user to the last subroute of it that was visited, or if none of its subroutes have yet been visited then it redirects to its first subroute to have been registered.
So for example if the loaded routing tree looks like this:
/main
|_/main_sub_1
|_/main_sub_2
/secondary
and the user starts at route /main/main_sub_2, then goes to /secondary, then goes to /main, they will be automatically redirected to /main/main_sub_2 since /main/main_sub_2 is the last subroute of /main to have been visited.
I know that I could implement this in react router by using
<IndexRedirect to={getLastSubRoute(parentRoute)}> where parentRoute is the full path of the parent <Route> tag, and getLastSubRoute is self-explanitory, but the problem with this is that I would need to add such an <IndexRedirect> tag to every single route I create, which is not optimal since the routes are loaded dynamically, there may be up to 100 subroutes, and much of the application's routing will be written by other people who I shouldn't be relying on to remember to add that tag under every <Route> tag they write.
Ideally, I should be able to apply some function or mixin to the base <Router> tag in the React routing definition to add this functionality to all routing underneath it, but I'm not sure where to start. How might I solve this problem?
Your best bet and possibly the simplest solution would be to set an onChange hook on one of the top level routes. The hook would get called with the next parameter, which would be the next route that the user would be going to.
You would also have the hierarchical structure of routes there (navigating through to parent and children of the parent), so you could dynamically redirect using the replace function, that gets passed in as a parameter also.
I implemented something similar for permission and role management. What I also did was to .bind my store to the function that I pass into the route hook. You could possibly store the route you'd like to redirect to on the user in the state tree. Basically what you refer to as getLastSubRoute.
...
<Route onChange={myRedirectFunctionThatHasStoreBound} .. >
... // other routes
</Route>
...
function myRedirectFunctionThatHasStoreBound(store, prev, next, replace, callback) {
const user = store.getState().user;
const redirectTo = getLastSubRouteForRoute(user, next);
if (redirectTo) {
replace(redirectTo);
}
// don't forget this is you list callback as a param
// your app might stop working, explanation below
callback();
}
If callback is listed as a 4th argument, this hook will run asynchronously, and the transition will block until callback is called.
EDIT: Keep in mind that this will only work if you are using react-router that's newer than or equal to in version to react-router 2.1

Resources