I am building a React Redux application and I am using <Prompt/> from react-router-dom to prevent a user from navigating away from a page when a specific icon is being rendered on the page.
It works fine except for when the params in the url change and then it fires the <Prompt/> when I don't want it to fire because I haven't actually tried to navigate anywhere. My url looks like:
http://localhost:8080/#/path/im/on?ids=1%2C24&from=1512518400000&searchRequest=e0007&to=1512604799000
When the ids in the url become successful, they are removed from the url but <Prompt/> is still fired.
Is there any way I can prevent <Prompt/> from firing when the url params change and the first part of the url stays the same? i.e. http://localhost:8080/#/path/im/on
Not sure if this is still relevant to you but you can do the following:
'message' prop in the Prompt component can either be a string or a function that returns a string or boolean.
<Prompt
message={(location) => {
return location.pathname.startsWith("your url")
? true
: `Are you sure you want to go to ${location.pathname}?`
}}
/>
if a function is passed to message prop then it 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.
here is the documentation link: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Prompt.md
Related
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.
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
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.
This is the code that works ...for pushing to different routes
sendToMessage = (item)=>{
let path = '/messages';
this.props.history.push({
pathname:path,
state:{
clientid:item.clientid,
clientname:item.clientname
}
});
}
So, when I click on the item from a different route it works perfectly and the component receives the props sent
But when I am on the /messages page already and click on the item, I am not receiving the props so I can update the page with the new data.
why is this happening?
I'd like it to work for all situations,
whether from same route or from different route.
Now, I do receive this warning
Hash history cannot PUSH the same path; a new entry will not be added to the history stack
This is how I am trying to access the props in the destination component
componentWillReceiveProps(nextProps) {
console.log('nextPRops : ',nextProps);
}
but shouldnt I be able to still receive the props that I am sending??
is there any other way to send props to other component? these components are not parent and child component, so cant really pass props like that.
More details : it's a feature where a user gets a notification of message and gets taken to the messages page to that specific message he/she clicked on. Now, it may happen that the user is already on that page when the notification appears. in that case, we still need to show the new message when the user clicks on it. although it was made to bring to different route but it should also work when client is already on that page hope this makes it a little more clear
Method 1:
You can use redux to store clientid and clientname in redux and always access it from redux store, so instead of trying to route, you can update the data in redux store and that will trigger your componentWillReceiveProps function
Method 2:
One more solution would be to change the routing path, in case you have pathname like client - change the path to client/:clientid - if you use the routing in this way then you wont get the warning when you try to route from same component to same component
As what the warning said by react-router, you can't push to the same page.
Alternatively, you can/should use component's state, by updating the component state, it will rerender the component.
state = {
clientid: '',
clientname: ''
}
sendToMessage = (item) => {
this.setState({
clientid: item.clientid,
clientname: item.clientname
});
}
In my controller, I use $state.go('home', {"parameters": paramValue, "parameter1": another value, "parameters3": another another value}, {reload:true}); and while this does attempt to go to the home state and passes the parameters to the url, the home state view keeps loading. If I copy the url with the parameters passed, and click enter, everything loads properly.
This is the process of going to the home tab.
-> the url changes to reflect that home state is trying to be reached
-> a second reload occurs when the url gets the parameters passed. The parameters do get passed successfully.
-> It keeps trying to load the home view.
The url is correct and works( parameters and all); as I can copy the link and access it. I've also toggled with reload to false.
Should I try transitionTo?
Hey all for anyone else having a similar kind of problem. The answer to stop the unneccessary reloading is to add the notify:false to state.go('main.home', params, {notify:false});