How to use back button with React Router - reactjs

I am having problem with ReactRouter.
Here how I init and run the router:
var routes = (
<Route handler={App}>
<DefaultRoute handler={Domains}/>
<Route path="login" name="login" handler={Login}/>
<Route path="signup" name="signup" handler={SignUp}/>
</Route>);
var config = {routes, location: Router.HistoryLocation};
const router = Router.create(config);
export default router;
$(document).ready(() => {
router.run( (Root) => {
React.render(<Root/>, document.getElementById('maincontent'));
});
});
Reading the docs, this way I expect that I when press the Back button of the browser, or use the goBack() method the browser go to the previous page.
Instead, when I use goBack() method, first remove an hash from the url (that the method transitionTo added), and only after call it again, it works. The same for the button.

Per the docs, if you want to use HistoryLocation you have to configure your server to handle it..
React Router Documentation - HistoryLocation
(a nodejs example):
app.get('*', function (req, res) {
res.render('index');
});
Otherwise, you'll need to use HashLocation as that will keep it on the same page.
If that is set correctly, you can debug what's going on in the router by receiving the 2nd prop in router.run -- state.
Here's the properties state contains.
Router.run(routes, function(Handler, state) {
console.log(state);
React.render(<Handler>);
});

Related

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

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>

React router is not reloading page when url matches the same route

I have following route. When I'm at /createtest page and doing history.push(/createtest/some-test-id) as it matches the same route component, it is not reloaded. Is there any smart solution? Or I need to check match params and implement logic to reload?
(react 16, router v4)
<Route path="/createtest/:testid?/:type?/:step?/:tab?" render={(props) => <CreateTest user={user} {...props}/>}/>
You could give the URL parameters as key to the component so that an entirely new component will be created when a URL parameter changes.
<Route
path="/createtest/:testid?/:type?/:step?/:tab?"
render={props => {
const {
match: {
params: { testid, type, step, tab }
}
} = props;
return (
<CreateTest
key={`testid=${testid}&type=${type}&step=${step}&tab=${tab}`}
user={user}
{...props}
/>
);
}}
/>;
You can try this
const Reloadable = (props) => {
const { location } = props
const [key, setKey] = useState(location.key)
if (location.key !== key) {
setTimeout(() => setKey(location.key), 0)
return null
}
return <>{props.children}</>
}
<Route path="/" exact render={({ history, location }) => (
<Reloadable location={location}>
<SomeComponent />
</Reloadable>)}
/>
I think something like this might help solve your problem
<Route key={'notReloadableRoute'} ... />
(static key, like a regular string)
See this doc.
https://reacttraining.com/react-router/web/api/Route/exact-bool
The exact param comes into play when
you have multiple paths that have similar names:
For example, imagine we had a Users component that displayed a list of
users. We also have a CreateUser component that is used to create
users. The url for CreateUsers should be nested under Users. So our
setup could look something like this:
Now the problem here, when we go to
http://app.com/users the router will go through all of our defined
routes and return the FIRST match it finds. So in this case, it would
find the Users route first and then return it. All good.
But, if we went to http://app.com/users/create, it would again go
through all of our defined routes and return the FIRST match it finds.
React router does partial matching, so /users partially matches
/users/create, so it would incorrectly return the Users route again!
The exact param disables the partial matching for a route and makes
sure that it only returns the route if the path is an EXACT match to
the current url.
So in this case, we should add exact to our Users route so that it
will only match on /users:

React. How to redirect early in a component lifecycle

This seems so simple, but I am new to react and trying different approaches and nothing is working for me. (BTW I am a regular contributor but working on my clients machine and can't remember my so password.)
The version of react router is 4.0, and state is stored using redux.
The scenario is that we are changing the order of routing in our application and would like to redirect any users that have Urls with the old structure to the new Url structure. I have tried the following (also note that I have "scrubbed" the names of the page, function calls and variables):
There is a trigger component for the section I need to direct from, with routing set up like this:
<Route path='page/:pageGuidGuid' component={PageTrigger}> </Route>
In the trigger component, ComponentWillMount makes a request that returns a 404 if the link is from the previous component, although it does redirect to the correct route. I am checking for and triggering the redirect in getInitialState, but the component keeps going through the lifecycle and ComponentWillMount is called, resulting in a 404 and then redirects to the correct page.
const PageTrigger = connectToStores(React.createClass({
getInitialState() {
this.checkForRedirect();
return {};
},
componentWillMount() {
if (this.props.params.guid) {
this.setCaseByGuid(this.props.params.guid);
}
},
checkFrorRedirect() {
/* logic to determine if it should redirect */
browserHistory.push(redirectUrl);
}
}
I have also tried creating a custom route...
<CaseRedirectRoute path='(cases/:caseGuid)' component={CaseTrigger}>
</CaseRedirectRoute>
And in a separate module (based off a login sample)
const CaseRedirectRoute = ({ component: Component, ...rest }) => (
<Route
{...rest} render={props => (
checkForCaseRedirect(...props) ? (
<Redirect to={{
pathname: getCaseUrlRedirectUrl(...props),
state: { from: props.location }
}} />
) : (
<Component {...props} />
)
)} />
);
I have WithRouter imported from react router. When I try to run this I am getting an error inside the react framework:
Uncaught Type Error: Cannot read property 'createRouteFromReactElement' of undefined
at RouteUtils.js:68
at forEachSingleChild (ReactChildren.js:52)
at traverseAllChildrenImpl (traverseAllChildren.js:98)
at traverseAllChildrenImpl (traverseAllChildren.js:114)
at traverseAllChildren (traverseAllChildren.js:186)
at Object.forEachChildren [as forEach] (ReactChildren.js:70)
at createRoutesFromReactChildren (RouteUtils.js:65)
at Function.createRouteFromReactElement (RouteUtils.js:35)
at RouteUtils.js:69
at forEachSingleChild (ReactChildren.js:52)
I've tried redirecting from app.js and page.js, but the PageTrigger is the first component having state set. I either need to figure out how to stop execution after the redirect, or figure out why my custom route keeps blowing up. Any help would be appreciated.
I'm not sure about your setup, but I would implement this redirect as this:
<Route path='oldurl/:p1/:p2' render={
routeProps => (
<Redirect to={'/newUrlHere/'+routeProps.match.params.p1} />
)
} />
So, if the route matches (you may specify exact match if necessary), when the Redirect "renders" it stops rendering and should start the rendering from the new URL. You may build your URL in the Redirect.to property as you wish

how to tell react-router to wait until something has happened?

I want to configure react-router to wait for Firebase to login (or logout) before rendering any route.
I've seen react-router has an onEnter() hook, but it seems this is an immediate callback, and I haven't quite figured out how I would make react-router wait until firebase triggers an firebase.auth().onAuthStateChanged(...) event.
Does react-router include some functionality to handle such cases?
The onEnter hook accepts a third parameter, callback?, which if specified will block loading of the route until it is called.
An example from the react-router repo:
function requireCredentials(nextState, replace, next) {
const query = nextState.location.query
if (query.qsparam) {
serverAuth(query.qsparam)
.then(
() => next(),
() => {
replace('/error')
next()
}
)
} else {
replace('/error')
next()
}
}
and the render method:
render((
<Router history={withExampleBasename(browserHistory, __dirname)}>
<Route path="/" component={App}>
<IndexRoute component={Form} />
<Route path="page" component={Page} onEnter={requireCredentials}/>
<Route path="error" component={ErrorPage}/>
</Route>
</Router>
), document.getElementById('example'))
However, there is a warning about using this for long-running operations in the documentation:
Caution: Using the callback in an enter hook causes the transition to wait until it is called. This can lead to a non-responsive UI if you don't call it very quickly.
I fixed it using a main component in my hierarchy that prevents children to be rendered until something has happened.
Here is the implementation. Not the most elegant code, but it works in case you are desperate as I was.

React router with browserHistory goes to server on every URL change

I am doing something like:
<Router history={browserHistory}>{routes}</Router>
When I do above whenever URL in address bar changes call is going to server but this is not what I want, I want first time page to load from server but after that whenever route change component should load in client side only. Am I missing something here?
In client side I am doing something like :
ReactDOM.render(
<Provider store={app.store}>
<Router history={browserHistory}>{routes}</Router>
</Provider>,
document.getElementById("app")
);
and my routes look like:
const routes = (
<Route path="/" component={DJSAppContainer}>
<Route path="page" component={DJSPage}>
<Route path="/page/:pageName" component={PageContainer} />
</Route>
</Route>
);
Now whenever I do location.href = "/page/xyz" it goes to server and load the content.
You shouldn't change location.href directly. You should send the new path to React using:
ReactRouter.browserHistory.push(newPath);
If you have anchor tags, you should use the <Link> component mentioned in #ahutch's answer.
React router exposes as props the history used in the current views. See their docs for all the props that are injected here.
If you want to redirect from DJSAppContainer or any of the two children views DJSPage or PageContainer you could do it by accessing the history property:
const {history} = this.props;
history.pushState('/path/to/new/state');
If on the other hand you do some fancy stuff and want to redirect from outside the components, try this (taken from react router docs)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
This two approaches assume you are trying to redirect based on some complex logic, not as a consequence of a click event. If you just want to handle click events, try using the <Link/> component of react-router.
It seems that the history property is now deprecated, and seems to be replaced by the router context property as seen in the implementation of the link component. Basically, if you really want your code to be future proof, you should ask for the contextType:
contextTypes: {
router: object
},
And then use it from the context of the component:
this.context.router.push('/some/path')
Assuming you're using node on the back-end, make sure that you have it set up according to the react-router docs.
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Also, when you're linking between components and you want to ensure that you are using react-router for the routing instead of the server, make sure to use Link. Hope that answers your question.
I was having a similar issue, while hashHistory worked without issue browserHistory would always load from the server. I got things working by remembering to call preventDefault(). Here is my function:
handleSubmit: function (e) {
e.preventDefault();
var username = this.usernameRef.value;
this.usernameRef.value = '';
this.context.router.push(`/profile/${username}/`);
}
handleSubmit is called on a form onSubmit.

Resources