React JS Routing issue on back button press - reactjs

I have one odd requirement in one of my project, the requirement is -
1) On initial load of my web-app it should load initial page (first page).
2) If the user comes next time then he should be directly redirected to where he left previously - I'm getting the DROP STAGE from API.
3) When user comes next time then he is redirected to the previously dropped page, but on pressing back he could be redirected to the previous page of the page where he left
Now what I have done so far is -
I have used react-router-dom for routing, but the thing which happens to me is -
1) User comes first time and he is redirected to the very first page by checking the DROP stage from API
2) When user comes again, he has a DROP STAGE (from API) so I am redirecting him to the DROPPED Page.
3) When user press back button (mobile device), the user doesn't stays to the page instead he is redirected again to the DROPPED PAGE as I am checking the DROP STAGE on componentWillMount event and he is again redirected.
Workaround which I have tried are -
1) To set a localstorage variable on the dropped page and check that variable on the back button pressed page - BUT THE RESULT IS I GET THE LOCALSTORAGE VALUE AS EMPTY
2) To check the Action of page i.e. when the user press back button the action becomes POP, but the problem is when the user comes for the first time then too the action is POP - HOW CAN I GET THE ACTION AS PUSH ON INITIAL LOAD
How can I achieve my functionality. Please help

What you need is a module which will be called only once when the application is loaded but it should be loaded irrespective of which ever page the user accesses first.
What you can do is add an empty component in your main router file which should be the first to be called and is called in all the routes. And in the componentDidMount of the component, you should handle the redirection no where else. So the redirection will happen only once thus handling the back actions automatically.

Related

React-Router v6: Can you change the destination of a <Prompt /> component?

I have a flow in my app that has the user going through a few pages.
Normal flow would be 1 -> 2 -> 3
I want to restrict the user from going back with a prompt, which is not an issue, but if the user performs an action on page 3, I don't want to allow the user to go back to page 2. Is there a way I can change the back button destination to page 1 when the user sees a prompt on page 3?
My first idea was creating a useEffect that would check if they should go to page 1 instead of page 2, and modify the history stack, then clean it up in the return function of the useEffect. I can't figure out how to accomplish this right now since react-router v6 removed useHistory. Is there a better way to do this? Maybe with useBlocker?
Thanks!

ReachRouter navigate in component constructor

If a user goes to a page that requires a context beyond what's on the url, I'd like to redirect them elsewhere. The use case is:
/todos/list - this page shows the user their list of todos. It contains links to:
/todos/edit?id=1 - this page allows the user to view/edit details about a particular todo.
If a user were to go directly to /todos/edit (with no id), I'd like to redirect them to /todos/list. I have tried doing this via navigate('list') conditionally in the constructor. This does update the browser url correctly, but it doesn't render the /todos/list page. Is this possible to do? Or is this not possible to do the para below?
I understand the more common url would be /todos/edit/1 so that reach router would handle my issue w/out me needing to deal with it. However, I'm just using this as an example of a piece of information required to render the page that isn't necessarily part of the the url path.
of course as soon as I type the question in stackoverflow, I find the answer is in the docs right in front of my face:
https://reach.tech/router/api/Redirect

Reactjs - How to Bring Users Back To Their Previous Spot of list item on back click

Can anyone help me with following issue:
Currently I am infinitely loading item list by giving page numbers in API call. Say I am calling item list api with page = 1 then page 2 (once it reaches end of 1st page items it loads next page) and so on.
Say currently I am on page 3. If user clicks any of the item on page 3, he will be redirected to details page. And if he clicks browsers back button, he should be back to clicked item of page 3 not at the top of page i.e first item of list.
How can I achieve this?
FYI componentDidMount calls api's first time and the later on onScroll method calls with next pages
Solution I tried:
keep clicked item position and page in redux. And once I am back to list I will load api's till saved 'page number' doesn't loads. And once expected page loads I will scroll to item position.
==> Problem with this solution is user will have to wait for response of expected page number and once expected page number's result comes it will scroll to clicked item position. So that seems annoying!
Use
<Router history={browserHistory}>
<Root>{routes(pages)}</Root>
</Router>
Add this in Your App.Js, Use browserHistory. Later In your component you can use something like
this.props.history.goBack();
Bind the action with button/anchor tag.
Thanks
You have to use localStorage to save the response that you got from the server and when the user comes back to your page you can show him the response immediately and also update the list in the background.
Service worker is a good way for doing this too .

How to wait for page to re-open

I have a page where extra features become available if you login. Login is via a popup panel. On successful login, the page is reloaded (so that options appropriate to user rights are dynamically added to the interface).
#Override
#Step("Logs in as: {0}")
public <T extends Actor> void performAs(T actor) {
actor.attemptsTo(
Open.browserOn(homePage),
Click.on(homePage.loginMenuItem),
Enter.theValue(authenticated(actor).username())
.into(homePage.userField),
Enter.theValue(authenticated(actor).password())
.into(homePage.pwdField),
Click.on(homePage.loginBtn),
);
}
The test uses a question to query state of the page. However, what I am seeing is that test seems to be operating on the page before it is reloaded. (times out waiting for target, returns old values elements on the page depending on how I do the question). I can see the revised page being displayed. Clearly, I need to wait till page is reopened in response to click before I ask the question which I thought selenium was supposed to do. But how do I do I wait? If I add:
Open.browserOn(homePage)
after click, then it works - mostly. Sometimes it still seems to respond to old page. But if I rerun, it usually works. Ie the above method is flaky. I could just wait 2 seconds, but theoretically it could still fail if authentication server was being slow. Ideas please.
On the line before
Click.on(homePage.loginBtn)
grab an element, like the login button and store it in a variable. Then click on the login button ... then wait for the login button to become stale. A stale element indicates that the page has refreshed. After that point, you are ready to access the page again.
I don't know what all helper functions you have available but it should look something like this
WebElement loginButton = driver.findElement(homePage.loginBtn);
Click.on(homePage.loginBtn)
new WebDriverWait(driver, 10).until(ExpectedConditions.stalenessOf(loginButton));
// do stuff

How does react-router persist location state over page refreshes?

I am just so very confused.
I have a workflow where someone can start to fill out a form for a product. It is a long form and I want to save progress to the server as they type (but not until they fill it out for a bit). So we start at a url for creating the form, after they've typed for a bit, we POST to create a resource on the server, and after the request finishes, we update the url to the edit route with the new id.
In other words, you start filling out the form at url /product then after you've filled it out for a bit the url shifts to /product/123. After that, loading that URL gives you your form.
So basically I have
<Route path={`/product`} exact component={CreateProduct} />
and
<Route exact={true} path="/product/:productId" render={({
match: {params: {productId}},
location: {state: {data}={}}
}) => (
<EditProduct productId={productId} initialData={data}
)} />
See that state? That's because the way I do the switch over from create to edit mode is something like this
const id = await apiFetch(`/api/product`, data, {method: `POST`})
this.props.history.push({pathname: `/product/${id}`, state: {data} })
in the constructor of my <EditProduct> component I have
constructor({productId, initialData}) {
this.super()
this.state = {}
if(initialData)
this.setState({data: initialData})
else
getProduct(productId).then(({data}) => this.setState({data}))
}
By doing that, the initial data into the <EditProduct> is seeded from the <CreateProduct> component and I don't need to reload it from the server or anything.
This works, the transition is smooth, the url updates, and everything is hunky dory.
I can now continue editing the <EditProduct> component and it saves properly. I can open a new tab to the same url and it loads everything up and I can continue. This happens because in that situation initialData is undefined so it's loaded from the server. yay!
BUT
If I instead refresh the original tab things get weird. Any changes that have accumulated since the save are lost. Drilling down in the debugger I see the issue is that initialData passed from the location.state.data object is not empty - it is the initial object from when the product was first created.
So where on earth does it come from? I just did a full page refresh (even a "hard" refresh with no cache and devtools open). That data isn't in the URL (and in fact copy pasting the url into another tab in the same window doesn't have this issue).
The only mechanism I'm aware of that can persist data across refreshes but not to new tabs like this is sessionStorage, yet when I check it in the console, I am told
> sessionStorage
< Storage {length: 0}
I've even thought that maybe react-router is manipulating session storage just before the page unloads and just after it loads, but breaking on the first line of my javascript bundle shows the exact same thing.
So how on earth is this persistence happening!?
I believe the asker already resolve this problem, the answer is buried in the comment though.
The question is actually down to this:
Where the state come from when the user reloads the page? And state refers to props.location.state provided by react-router
TLDR; the state is not a plain javascript implementation, it is bound to the browser environment.
The BroswerRouter of react-router use the underlying native broswer history API directly so the history API is bound to the platform, you can not predict its behavior based on the normal rule.
Here is the special part:
The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts the browser
Most of the users treat the state as a plain javascript, so there is a problem
I had a very similar problem, and the same confusion.
Solved it with window.history.replaceState()
I had a simple search form which redirected to a second page, and used the location state from the router to repopulate the search input on the second page.
In myse case, this happened:
Search for "foo" on the first page -> Get redirected to the second page, and see search+results for "foo".
Search for "bar" on the second page. -> See results for "bar".
Hit refresh. Expectation? Either an empty search bar, or search+results for "bar". -> Instead, see search+results for "foo" (??)
I solved this by making it so that every time the user does a search on the second page, I replace the state using window.history.replaceState with the correct search term. This way a refresh gives the user the expected search. Replacing the state with an empty object on each search from the second page worked fine as well, giving the user an empty search on each refresh.

Resources