Following problem:
const history = createBrowserHistory()
render() {
return (
<Router history={history}>
<WButton history={history}/>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path='/next' component={Next}/>
When I do this.props.history.push("/next") in WButton Component it only updates the url not switches to Next Component. When I move WButton into Home Component everything works correctly but I'd like to keep WButton on top level. Is this somehow possible?
With createBrowserHistory({ forceRefresh: true }) it also works on top level but I don't want to reload page with each navigation step.
Found the solution:
I could just use export default withRouter(WButton) in the WButton Component instead of the standard export and then remove the manual injection of the history via the props.
Import is import { withRouter } from 'react-router-dom';
Related
I have two apps within single-spa, one in React and other in Vue.
The React app uses history library for navigation. Below given are my React app files:
history.js
import { createBrowserHistory } from 'history'
export const history = createBrowserHistory({
basename: '/myapp,
forceRefresh: true
})
App.js
import React, {Component} from 'react';
import { Router, Switch, Route } from 'react-router-dom';
import history from ‘../history.js’;
class App extends Component {
constructor(props){
super(props);
}
render(){
return (
<Router history={history}>
<Switch>
<Route exact path="/user" component={User} />
<Route exact path="/" component={Home} />
</Switch>
<Router />
)
}
}
I face an issue when:
I’m at the path https://localhost:3000/myapp/user of React app and I switch to the Vue app (using navbar menu).
Now when I switch back to React app, the url shows https://localhost:3000/myapp which ideally should load my Home component.
But now the history still has the old location (https://localhost:3000/myapp/user) which then loads the old User component.
Is there a way to update history when the url changes?
I didn't find how to change history, but I want you to fix some grammar errors. Meybe fixing errors can help you.
history.js
export const history = createBrowserHistory({
basename: '/myapp, // basename: '/myapp',
forceRefresh: true
})
App.js
<Switch>
<Route exact path="/user" component={User} /> // <Route path="/user" component={User} />
<Route exact path="/" component={Home} />
</Switch>
I think this boils down to getting to routing mechanisms to work together. This is an interesting problem and I have two ideas that could work:
Idea 1: refresh the page
If you're okay with a full reload, refreshing the browser whenever you switch between Vue and React would probably reset the state in both routers so that they both read the initial state they loaded on and continue.
I would do this by assigning window.location.href = '/myapp' inside any event handler that would cause the transition to React. It should reload the browser and navigate to that page. This should work if your react app is loading at that path.
Idea 2: Use createMemoryHistory and sync it with Vue's router
Instead of feeding react-router a "browser router", you can instead pass it a "memory router". As the name implies, the memory router holds state in memory (instead of using the History API).
I'm thinking you can wire up the events from Vue's router to react-router's memory router and get them in sync like that. I think you can call history.push inside of Vue's global after hooks.
Something like this:
// 🔴 I have not tested this, take this as inspiration
const router = new VueRouter({/* ... */});
const history = createMemoryHistory({/* ... */});
router.afterEach(({fullPath}) => {
history.replace(fullPath, {fromVueRouter: true});
});
history.listen(location => {
if (location.state?.fromVueRouter) return;
router.replace(location.pathname);
});
I'm trying to set up a history listener at the topmost level so that every time the location changes, some code is executed (Google Analytics tracking in my case).
Currently, my App.js looks like this:
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Home}/>
<Route path="/" component={MainLayout}/>
</Switch>
</BrowserRouter>
);
}
As I understand, I can only use history inside BrowserRouter, but it can only have one child, so I can only put it inside Switch, but then I'll have to put it inside every component under Switch, which I can't do since I want it to run only once. If I just add a custom component under Switch without wrapping it into a Router, there's no history.
What should I change to make it work? Is it possible to get an instance of history in the App.js code? Is it possible to achieve using a custom component or a HOC?
You need to add a Route which will get rendered unconditionally.
Try something like this:
render() {
return (
<BrowserRouter>
<React.Fragment>
<Route component={HistoryListenerComponent}/>
<Switch>
<Route path="/" exact component={Home}/>
<Route path="/" component={MainLayout}/>
</Switch>
</React.Fragment>
</BrowserRouter>
);
}
So HistoryListenerComponent is always rendered and you can listen from its componentDidMount.
Heres what worked for me:
import {withRouter} from 'react-router-dom';
Now just wrap the component where you are using React-ga with withRouter like this:
componentDidMount=()=>{
ReactGa.initialize('00000'); //enter id
ReactGa.pageview(window.location.pathname + window.location.search);
this.props.history.listen(location => {
ReactGa.set({ page: location.pathname }); // Update the user's current page
ReactGa.pageview(location.pathname); // Record a pageview for the given page
});
}
render (){
return (
<Switch>
<Route ....>
.....
<Switch>
);
}
export default withRouter(ComponentName)
Remember without wrapping withRouter , you cant use this.props.history property.
You cant use custom history listener to BrowserRouter component. It has an inbuilt history prop (i.e this.props.history).
It is possible using history package, instead of relying on the default one created by react router.
For instance, using history.listen:
history.listen((location) => {
ReactGA.pageview(location.pathname + window.location.search);
});
In my exemple, I use react-ga, as a way to standardize our GA Instrumentation across projects.
Found out that the easiest option is the Update component -- https://github.com/pshrmn/rrc/blob/master/docs/OnUpdate.md
I need to redirect to home page when user refreshes other pages inside my application. I am using React router v4 and redux. Since the store is lost on reload, the page user reloaded is now empty and hence I want to take him back to a page that does not need any previous stored data. I don't want to retain state in localStorage.
I tried to handle this in event onload but it did not work:
window.onload = function() {
window.location.path = '/aaaa/' + getCurrentConfig();
};
You can try creating a new route component, say RefreshRoute and check for any state data you need. If the data is available then render the component else redirect to home route.
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
const RefreshRoute = ({ component: Component, isDataAvailable, ...rest }) => (
<Route
{...rest}
render={props =>
isDataAvailable ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/home"
}}
/>
)
}
/>
);
const mapStateToProps = state => ({
isDataAvailable: state.reducer.isDataAvailable
});
export default connect(mapStateToProps)(RefreshRoute);
Now use this RefreshRoute in your BrowserRouter as like normal Route.
<BrowserRouter>
<Switch>
<Route exact path="/home" component={Home} />
<RefreshRoute exact path="dashboard" component={Dashboard} />
<RefreshRoute exact path="/profile" component={ProfileComponent} />
</Switch>
</BrowserRouter>
It is so amazing that you don't want to keep state of user route map in browser but you use react-router!, the main solution for your case is do not use react-router.
If you don't use it, after each refresh the app come back to main view of app, If you wanna see route map in address bar without any reaction use JavaScript history pushState.
Hope it helps you.
I am trying to test a component which listens to the history being changed by creating a wrapper in Enzyme which places my component within a MemoryRouter; i.e.:
mount(
<MemoryRouter initialEntries={'/path/param1}>
<Switch>
<Route
path="/path"
component={MyComponent}
/>
</Switch>
</MemoryRouter>
)
For the initial path, this works fine, however, I specifically want to test what happens when it starts at say /path/param1 but then the history is changed to /path/param2
The monitoring of the path is being done by wrapping the component's export with withRouter, like this:
export default withRouter(MyComponent)
And then upon construction, I am using history.listen to subscribe to the history changes.
You can use a base <Route> component with a custom history object to do this.
First, install the history package (to devDevpendencies if you are only using it in tests).
In your test file:
// we are using memory history only
import { createMemoryHistory } from 'history';
import { Router, Route} from 'react-router-dom';
Then, setup your component like so: (you can drop the Switch if there is only one route)
const history = createMemoryHistory({
initialEntries: ['/path/param1'],
});
// mount with custom history object
mount(
<Router history={history}>
<Route
path="/path"
component={MyComponent}
/>
</Router>
)
Later in your test, you can check what's the current location of the history after your component does its thing.
// check current path has been changed
expect(history.entries[0].pathname).to.eq('/path/param2');
You can find all the other stuff you can get access to with history here:
https://github.com/ReactTraining/history
When a url param change, I need to update two components, but one of them is outside the route with the param. The routes in App.js are like this:
<BrowserRouter>
<div>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
<Route path="/" render={ props =>
<ProductHistory {...props}/> }
/>
</div>
</BrowserRouter>
The ProductHistory which is always visible has links pointing to products, like:
<Link to={`/products/${product.product_id}`}> {product.name}</Link>
When following such a link, the Product component is updated using ComponentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.match.params.product !== this.props.match.params.product){
But how do I update the ProductHistory component at the same time when the product param change? Since it isn't within the /products/:product route, checking this.props.match.params.product in ProductHistory's componentWillReceiveProps results in undefined.
(edit - and withRouter doesn't help, since it already is within a route, but a different one: "/")
In componentWillReceiveProps I could use location.pathname to check that the path begins with "/product", and I could find the param by substr(path.lastIndexOf('/') + 1.
Edit: But I also have to compare the current id param with the next product id param to avoid unnecessary updates. But when clicking the link, the url have already changed when componentWillReceiveProps fires so location.pathname and nextProps.location.pathname always match, so it updates unnecessarily (repeated api calls).
So I would have to find a different solution - rearrange the routing in some way? The idea is that ProductHistory should always be visible though.
You can render the Route simply like this:
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" render={ (props) =>
<Home products={this.state.products} }
/>
<Route path="/products/:product" render={ (props) =>
<Product {...props} /> }
/>
</Switch>
<ProductHistory />
</div>
</BrowserRouter>
And then in the ProductHistory class you use the withRouter HOC
You can get access to the history object's properties and the closest
Route's match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
example:
class ProductHistory extends Component { ... }
export default withRouter(ProductHistory);
or using decorators
#withRouter
export default class ProductHistory extends Component { ... }
With this you will be able to access match, location and history through props like this:
this.props.match
this.props.location
this.props.history
For anyone stumbling across this, there is a new solution afforded by hooks, in the form of useRouteMatch in react-router-dom.
You can lay your code out like João Cunha's example, where ProductHistory is not wrapped within a Route. If the ProductHistory is anywhere else but inside the Route for products, all the normal routing information will seemingly give the wrong answer (I think this might have been where the problems with withRouter arose in the replies to João's solution), but that's because it's not aware of the product route path spec. A little differently from most MVC routers, React-router-dom won't be calculating the route that matched up front, it will test the path you're on with every Route path spec and generate the specific route matching info for components under that Route.
So, think of it in this way: within the ProductHistory component, you use useRouteMatch to test whether the route matches a path spec from which you can extract the params you require. E.g.
import { useRouteMatch } from 'react-router-dom';
const ProductHistory = () => {
const { params: { product } } = useRouteMatch("/products/:product");
return <ProductList currentProduct={product || null} />;
};
This would allow you to test and match against multiple URLs that might apply to products, which makes for a very flexible solution!