React router and useCallback - reactjs

I have the following routes:
const makeIndexRoutes = (): React.ReactElement => (
<Switch>
<Redirect exact from="/" to="/estimates" />
<Route exact path="/estimates" component={CostingPage} />
<Route exact path="/estimates/new" component={NewEstimatePage} />
<Route exact path="/estimates/edit/:id" component={EditEstimatePage} />
</Switch>
);
And in another file I try to make a redirect on button click like this:
const handleClose = useCallback(() => {
// do some action on 'close' button click
<Redirect to='/estimates'></Redirect>
}, []);
But nothing happens, is anyone able to guide me on what I am potentially doing wrong?

This won't work, what you want to is programmatically redirect.
For this, you should do something like this:
import { useHistory } from 'react-router-dom';
const history = useHistory();
const handleClose = () => {
history.push('/estimates');
});

What you are doing will not work because Redirect component should be rendered in JSX in order for it to work and change the route.
You can use one of the following options to change the route
Use history object from router props
props.history.push('/estimates')
you could also use useHistory hook provided by react router to access the history object.
Use Link component provided by react router. It will automatically change the route without needing a click listener
<Link to="/estimates">Estimates</Link>
For more details see:
Link Component
history

Related

HIghlight Active Link in the Navbar , React

This is a simple version of the problem not the actual problem.
React code:
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
This problem is that in the Navbar, I have several button to go the particular genre.
I want to highlight active button in the Navbar (when I click "comedy" the "comedy" button should be lit up)
some solutions
State => will not work if I reload
Session Storage => will not work if I come be website for the first time
Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar
Extract from the window.location => Don't want to do it since it look too ad-hock
What is the right method?
I would suggest #3 "Extract the params using the useParams Hook => this will not work since param "genreId" is available to the movieList component not the navbar". You are correct though, this doesn't work since the Navbar component is rendered outside the Routes component and won't have access to the currently matched route. To resolve this you should move the Navbar component inside the Routes component. To make this work you'll create a layout route component that renders the Navbar component and an Outlet component for nested routes to render their element prop into.
Example:
import { Outlet } from 'react-router-dom';
const Layout = () => (
<>
<Navbar />
<Outlet />
</>
);
Then render the MovieList route as a nested route of the layout route.
<Routes>
<Route element={<Layout />}>
<Route path="/:genreId" element={<MovieList />} />
</Route>
</Routes>
The Navbar component can now safely access the genreId route path param and use it to apply any logic necessary to mark a button as "active. Here's a simple example:
const genres = ["action", "comedy", "drama"];
const Navbar = () => {
const navigate = useNavigate();
const { genreId } = useParams();
const navigateTo = (genreId) =>
navigate(generatePath("/:genreId", { genreId }));
return (
<>
{genres.map((genre) => (
<button
key={genre}
className={["genreButton", genreId === genre && "active"]
.filter(Boolean)
.join(" ")}
type="button"
onClick={() => navigateTo(genre)}
>
{genre}
</button>
))}
</>
);
};
I usually use IndexDB for that kind of stuff. To manage the IndexDB perfectly you can use localforage. You can check this link.localforage
You can use NavLink instead of Link to design the active route. Here is the documentation.
<Router>
<Navbar/>
<Routes>
<Route path="/:genreId" element={<MovieList/>} />
<Routes>
<Router>
In the Navbar component you can write like this to have the active link:
<nav>
<NavLink to="/:genreId">Genre Name</NavLink>
</nav>

React Router nesting reloads page

I'm trying to set up nested routes in React using React Router so that the nested components load directly, however the page reloads when I attempt to go to a nested route.
Even the official example does the same - https://reacttraining.com/react-router/web/example/nesting The official example works as expected when it is opened in a new window.
One thing I noticed was that if I actually change the route from within one of the child route components the page does not reload. But this is bad practice and I want to change the route in the component that defines the routes.
Has something changed recently? How can I achieve nested routes changes without page reload?
Parent
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// At route /home
const Parent = (props: RouteComponentProps<any>) => {
const changeRoute = () => {
props.history.push('/home/test'); // Reloads page
};
return (
<Router>
<Switch>
<Route component={Test} path="/home/test" />
<Route component={Default} />
</Switch>
<button onClick={changeRoute}>Click</button>
</Router>
);
};
export default withRouter(Parent);
Child
const Default = (props: RouteComponentProps<any>) => {
const changeRoute = () => {
props.history.push('/home/test'); // Does not reload page
};
return (
<button onClick={changeRoute}>Click</button>
);
};
export default withRouter(Default);
I'm using react-router-dom v5.1.2.
import {HashRouter as Router} from "react-router-dom";
change BrowserRouter to HashRouter and check ,it stops reload issue
The problem was that I was using <Router> in Parent even though Parent was itself within <Router> tags. Replacing <Router> with <div> in Parent fixed the issue.

Link to not rendering the component again

I am using React Link to="" property. I have a url like below.
http://localhost:3000/work/109
After clicking on link, It is successfully going to url like below, But not re-rendering the component again.
http://localhost:3000/work/107
Below is my file, where i am using react-router
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const RouteWithSubRoutes = route => (
<React.Fragment>
<Route exact path="/" render={()=>(<Redirect to="/home" />)}/>
<Route exact path={route.path} render={props => (
<route.component {...props} routes={route.routes} onOpenNav={route.onOpenNav}/>
)} />
</React.Fragment>
);
Is there any other property of React, which i am not using.
Note: I am going to same url, But with diffrence id.
you should use 'react-router' params in your Route component path.
also this way new params would send to your component after clicking on Link component.
you could check this.props.match.params to sure it gets update.
then since you want component to re-render you should use getDerivedStateFromProps to get new value and set it to your state.

React Router v4 - Redirect to home on page reload inside application

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.

React router: How to update component outside route on param change?

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!

Resources