ReachRouter navigate in component constructor - reactjs

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

Related

React Router setup for 'unlimited' params

I am trying to implement the React Router in such a way that it supports a route like this:
/myPages/:pageName1/:pageName2/:pageName3/...
The idea is that, even though the page being rendered is only the last page, the pages are nested, and the depth is not something that is pre-determined. The component that renders the actual page needs to know the names of parent pages.
To clarify, the idea is that the page data in the backend are in a tree structure in such a way that, in the above example, page1 is the root page, page 2 is a child of page1, page 3 is a child of page2, etc. In addition, one page can have multiple children. The last child name (so pageName3 in the example) is what is being used to query the database and get all the content required to render the full page. The parent names are required to render navigation-related subcomponent. I should also mention that just having /myPages/:pageName3 and getting all parent names from the backend is not really an option. I could fetch that information from the backend, but the URL presented to the user still needs to have that structure.
I am hoping that there's a way to get this type of information as an array, but I am having a hard time finding an example like this on the web.
maybe this can help.
https://github.com/ReactTraining/react-router/blob/d28d46dce08a5756a085f7e5eebb5169ea59e40b/packages/react-router/docs/api/Redirect.md#from-string
states:
A pathname to redirect from. Any valid URL path that path-to-regexp#^1.7.0 understands.
maybe you can combine
<Redirect from='/users/:id' to='/users/profile/:id'/>
with
var re = pathToRegexp('/:foo+', keys)
(taken from https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#one-or-more)
then you'll end up with
<Redirect from='/:pageName+/:id' to='/:id'/>

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.

See list of history through react-router props

I currently have a button that onClicks to history.goBack but I want it to direct the user to another path if there isn't a previous page (in the case the user visits the page directly by typing in the URL in the URL bar instead of clicking within the site to get there). When I checked out this.props.history there isn't an object containing the history of pages visited or a boolean that tells me if there is a page for history.goBack to work on. How do I check that? Looking at the docs for history library, there is suppose to be an entries property but this somehow didn't make it into react-router.
You can use go() property to move further back.
Eg. go(-1) will be equivalent of goBack() and go(1) will be equivalent of goForward().
I don't think there is a way to list all the paths on the stack, but you could allways implement history that you can push to and control.

Prevent deep link in react-router

In my application I'd like to have certain portions of the app not be able to deep linked to. For example our users have a list of surveys and I'd like if someone tried to go directly to a particular survey directly such as /survey/1 that react router would pick up on this and immediately redirect them back to /survey and they would have to select the one they want. I've tried to write onEnter hooks but they seem to be very cumbersome since the only way I've been able to get them to behave correctly is to store some global state that says they have been to the main page and inspect that every time the route is navigated to.
Im using pushstate in my application if that makes any difference and react-router 2.0
I'd like to try to avoid having to write server rewrite rules for this since there are a lot of areas in my application where this rule is applicable.
I have a suggestion which is similar to the onEnter hook:
Wrap the component of the survey/:id route with a function which verifies if deep linking is allowed or not, let's call this function preventDeepLinking
The preventDeepLinking function checks if the location state contains a certain flag, let's say allowDeep. This flag would be set in the location state when navigating from another page of your app. Obviously, this flag will not be set when the user tries to navigate directly to the page of a survey.
The preventDeepLinking function will render the wrapped component only if deep linking is allowed, otherwise will redirect to a higher route.
I created a sample on codepen.io. You can play with it in the debug view of the Pen: http://s.codepen.io/alexchiri/debug/GZoRze.
In the debug view, click the Users link and then on a specific user from the list. Its name will be displayed below. Notice that its id is part of the url. Remove the hash including the ?_ and hit Enter. You will be redirected to /users.
The code of the Pen is here: http://codepen.io/alexchiri/pen/GZoRze
The preventDeepLinking function can be improved, but this is just to prove a point. Also, I would use the browserHistory in react-router but for some reason I couldn't get it running in codepen.
Hope this helps.

Backbone - Get Router parameter and reload same route

I have a table and I'm deleting a row. When I delete this row, I want to reload the current Backbone route, but few things to take in count:
e.g I'm in this route
"oneurl#order-by-name/page-8"
If the table, at that page, has only one record and I delete it, I would like to redirect to #order-by-name/page-7, so I need to get the "page" parameter. If there are still records, I want to "reload" the same route (to add next page's row if there are). It seems is not possible (https://github.com/jashkenas/backbone/issues/1214).
Any ideas?
As discussed in the issue you link (mainly jashkenas's comment "trigger: true is usually a code smell"), you shouldn't really be using routes like that for a javascript application.
What you should be doing instead:
have somewhere to store the current application's state (e.g. a plain javascript object) which is passed to the function taking care of displaying the views and data you want
when a row is deleted, modify the application state object (e.g. changing the current page attribute) and call the same function as above, once again passing in the configuration
update the url with navigateonly if the fragment should change
In reverse, your router should parse the url fragment to generate a configuration object discussed above, and call the function to render the views (passing in the configuration info).
For more on how to manage routing without using trigger: true everywhere, take a look at http://lostechies.com/derickbailey/2011/08/03/stop-using-backbone-as-if-it-were-a-stateless-web-server/ and the free preview to my book (on Marionette, but the same applies to Backbone) which also contains a chapter discussing route handling.

Resources