I am using a child route as follows
<NavItem eventKey="player_list">
<NavText>
<Link
className={Styles.navText}
to={`${this.props.match.url}/player_list`}
>
Player
</Link>
</NavText>
</NavItem>
<main>
<Switch>
<Route
path={`${this.props.match.path}/player_list`}
component={props => (
<PlayerList {...props} basePath={this.props.match.path} />
)}
/>
</Switch>
</main>
whenever I click on the Link, the PlayerList Component is remounted. How to block this behaviour
From the docs:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop.
Instead what you can do is to use render function:
<Route
path={`${this.props.match.path}/player_list`}
render={props => (
<PlayerList {...props} basePath={this.props.match.path} />
)}
/>
Docs on render
Related
I have the following components structure:
App
Header
Home
Categories
Products
ProductList
ProductDetail
ProductDetailMore
Main Route:
<div>
<Header />
<Switch>
<Route exact path="/" render={() => <Home />} />
<Route path="/category" render={() => <Categories />} />
<Route path="/products" render={() => <Products />} />
</Switch>
</div>
The Route in Products:
const productsData = [...];
return (
<div>
<div>
<ProductList data={productsData} />
</div>
<Switch>
<Route path={`${match.url}/:productId`} render={()=>
<ProductDetail data={productsData} />} />
<Route exact path={match.url} render={()=> (
<div style={{ textAlign: "center" }}>Select a product.</div>
)} />
</Switch>
</div>
);
And the route in ProductDetail:
return (
<>
<div>
<h3> {product.name} </h3>
Links:
<div>
<NavLink activeClassName="active" to={`${match.url}/detail/${ "100"}`}>
Click me
</NavLink>
</div>
</div>
<div>
<Route path={`${match.url}/detail/:code`} render={()=>
<ProductDetailMore />} />
</div>
</> );
When I click on 'Click me' the value 100 is correctly displayed in my ProductDetailMore component but all the components are re-rendered (Products, ProductList, ProductDetail, ProductDetailMore); so, my questions is, how can I prevent a re-rendering in the parent components [Products, ProductDetail]?
And especially, I would like to avoid a re-render in ProductList, the one that is not in a Route?
You cannot really generally avoid rerenders, as react already decides which components need a rerender. However, this only means you can't avoid rerendering the parent components. With the react dev tools you can analyze why they rerender (it's an option in the profiler) and possibly find unnecessary causes for rerenders.
But these are the good news:
What you can easily do is preventing sub-components to rerender. For Example "ProductList". One Way would be the React.memo HOC used directly in the export of the component or in the "Categroies" component (at a static location, not in the render function):
const ProductListMemo = React.memo(ProductList)
Then, you're using "ProductListMemo" in the render function. This way, when the component is rendered, React checks beforehand if any props changed. If not, the component is not rerendered. Your code is somehow incomplete though. You define
const productsData = [...]
If this is in the render function, a new array will always be created and even if the contents are the same, React.memo will see a new array and rerender the component. You have to move the array outside of the render function or you have to wrap it in a useMemo (if you're not using class components):
const productsData = useMemo(() => [...], []);
This "useMemo" hook can also be used to avoid rerenders of components, you could use
{useMemo(() => (<div>
<ProductList data={productsData} />
</div>), [productsData])}
This way, every rerender react checks if "productsData" changed, and only then rerenders the components.
So the important thing to know is that if a parent component rerenders because of a state update for example, it will rerender every child component. And these will also rerender every of their child components. With a React.memo or a useMemo however, you can help react to decide to use a previously rendered component instead.
Im new to react. I want to redirect to a different component along with some parameters.
As of now, Im rendering a Hook Component from App Component with
App.tsx
<Hook region={this.state.region} loginUser={this.state.user.username}/>
But now I've a homePage.tsx, where I've a material ui component CardActionArea, I want to make the whole card component clickable & navigate to Hook component with " /userinfo " url Path and props data (But the param's should be visible in URL) after clicking the Card component.
So in App.tsx , I have
<HomePage />
In HomePage.tsx,
<Card className={classes.root}>
<CardActionArea>
<CardContent>
UserInfo
</CardContent>
</CardActionArea>
</Card>
Now how to Call Hook Component after clicking card component in HomePage.tsx with url="/userinfo" and data region & username . (Note: these data(region , username) should not be visible in Url path)
Redirection based on Url works with React Router library (run npm install react-router-dom to install). This should work:
App.ts
function App() {
// Wrap your HomePage inside a router to access history context
return (
<Router>
<Route path="/" component={HomePage}/>
</Router>
);
}
HomePage.tsx
import {withRouter} from 'react-router-dom';
function HomePage(props) {
const goTo: Function = (url: string) => {
props.history.push(url);
};
return (
<div>
<Card className={classes.root} onClick={() => goTo("/userInfo")}>
<CardActionArea>
<CardContent>
UserInfo
</CardContent>
</CardActionArea>
</Card>
<Router>
<Route
path="/userInfo"
render={props => <Hook {...props} foo="bar"/>}
/>
</Router>
</div>
);
}
// instead of export default HomePage;
export default withRouter(HomePage);
Some explaination :
Router component will basically iterate through every child Route. Each time the current URL matches a path prop, it will render the given component. Some route context will automatically be passed to child components afterwards. This allow the given child component to change URL and re-render the Routes, if needed.
HomePage is wrapped in Router, so it can manipulate URL and update render. Since you didn't specified any history context, all the Routers will use the same default one. Now let's say your user is located on the root URL "/". HomePage is rendered and visible, and so is the Card component, but Hook is not.
The onClick method will trigger the goTo local function, which will basically push a new entry into your browser history (this update the URL and refresh the render). Now both "/" and "/userInfo" match current URL (a match occurs when the current URL starts with the one given by the path).
If you want to pass hidden props, and this props are accessible by the parent component, then use render prop instead of component. This allows you to pass a functional component to Route, to which you can freely add and/or remove props.
So in this example, I just added the foo prop to Hook, but you can do pretty much whatever you want here.
EDIT
If you don't want your HomePage and your Hook to render simultaneously, then either set HomePage path to something different (like "/home") or add the exact prop like so :
App.ts
function App() {
// Wrap your HomePage inside a router to access history context
return (
<Router>
<Route path="/" exact component={HomePage}/>
</Router>
);
}
This will force an exact match, aka it won't render if the URL is not exactly "/"
App.tsx:
<Router>
<Route exact path="/" component={HomePage} />
<Route path="/userinfo" render={props => <Hook {...this.props} region={this.state.region} loginUser={this.state.user.username}/>} />
</Router>
HomePage.tsx
<Link to="/userinfo">
<Card className={classes.root} >
<CardActionArea>
<CardContent>
UserInfo
</CardContent>
</CardActionArea>
</Card>
</Link>
What is the difference between routing to a component like this:
<Route path="coolPath" component={MyComponent} />
or
<Route path="coolPath" render={props => <MyComponent {...props} customProp="s" } />
To this:
<Route path"=coolPath">
<MyComponent />
</Route>
or
<Route path"=coolPath">
<MyComponent cusomProps="cp"/>
</Route>
first you should read through this site:
https://reacttraining.com/react-router/web/api/Route
But to explain, there's three things going on here, the first two are examples of routing with previous version of react-router (before v5) and the third is react-router (v5 - current) recommended approach.
1. Route with component
<Route path="/coolPath" component={MyComponent} />
This type of route renders the single component passed to the prop. If an inline function is passed to the Route's component prop, it will unmount and remount the component on every render via the use of React.createElement. This can be inefficient, and passing custom props via this method is only possible via an inline function. React Router's authors recommend using the render prop as opposed to the component prop for handling inline functions, as shown below.
2. Route with render
<Route path="/coolPath" render={props => <MyComponent {...props} customProp="s" } />
Instead of having a new React element created for you using the component prop with an inline function, this route type passes in a function to be called when the location matches and does not unmount a component and remount a brand new one during rerender. It's also much easier to pass custom props via this method.
3. Route with children as components
<Route path="/coolPath">
<MyComponent customProp="s" />
</Route>
This is currently the recommended approach to routing, the child components will be rendered when the path is matched by the router. It's also very easy to pass custom props with this method.
Keep in mind there is a fourth type, which is:
4. Route with children as function
From reacttraining.com:
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Link,
Route
} from "react-router-dom";
function ListItemLink({ to, ...rest }) {
return (
<Route
path={to}
children={({ match }) => (
<li className={match ? "active" : ""}>
<Link to={to} {...rest} />
</li>
)}
/>
);
}
ReactDOM.render(
<Router>
<ul>
<ListItemLink to="/somewhere" />
<ListItemLink to="/somewhere-else" />
</ul>
</Router>,
node
);
Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.
In my main component, I have have an array of components that I pass to a child component (in a route) as a prop (this.state.commonProps.leftNavItems). When I navigate to the child's route and the child component renders, I noticed that the components passed to it as a prop are not the same instances but are instead rendered as new components (new componentDidMount) using their default values (not the current state as exist in the parent). Is there a way to pass the component instances themselves and keep their state, or can you only pass the component definitions and so would end up mounting brand new components when you pass them as props to children?
<Route
exact
path="/"
render={props => (
<Layout
leftNavItems={this.state.commonProps.leftNavItems}
/* mainItems={simulationRunRequestsMainItems} */
mainItems={null}
/>
)}
/>
<Route
exact
path="/simulation-runs"
render={props => (
<div>
<SimulationRun
commonProps={this.state.commonProps}
refreshSimulationRuns={this.refreshSimulationRuns}
/>
</div>
)}
I have a component which cannot traditionally inherit props from a parent component. This component is rendered via a route and not by a parent, I am talking about the <Single /> component which is the 'detail' component in this setup:
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={ProfileList} />
<Route path="/profile/:username" component={Single} /></Route>
</Router>
Props are available in the ProfileList component and that component renders a Profile component like so:
/* ProfileList render method */
render() {
return (
<div>
{this.state.profiles.map((profile, i) =>
<Profile {...this.state} key={i} index={i} data={profile} />)}
</div>
);
}
I am trying to reuse the Profile component in both the ProfileList and Single component:
<Link className="button" to={`/profile/${username}`}>
{name.first} {name.last}
</Link>
But in the Single component I have no way of accessing state or props - so I have no way of rendering this details view. I know I can either use redux for passing global state or use query parameters in my Link to=""
But I don't want tor reach out for redux yet and don't feel right about query params. So how do I access something like this.props.profiles in my Single component?
the redux connect() can completely do the job. I think you should use it, because "in fine" you will reimplement a redux connect like