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.
Related
I have a render function inside App.js.
return (
<div className="container">
<h1 className="text-center main-title">Activity Logger</h1>
<Router>
<NavigationBar />
<Route exact path="/">
{this.renderForm()}
</Route>
<Route path="/activitydisplay">{this.renderTable()}</Route>
</Router>
</div>
);
}
The Router is a BrowserRouter. The functions it call are
renderForm = () => {
if (this.state.formDataError) {
return "Error loading data";
}
console.log("renderForm was called");
return (
<div className="mt-3">
<ActivityForm
machinesList={this.state.machinesList}
operatorsList={this.state.operatorsList}
onFormSubmit={this.postFormData}
postSuccessCount={this.state.postSuccessCount}
loggedOperator={this.props.cookies.get("logger") || null}
/>
</div>
);
};
renderTable() {
if (this.state.tableDataError) {
return "Error loading data";
}
return (
<div className="mt-3">
<ActivityDisplay
activityData={this.state.activityData}
machines={this.state.machinesList}
operators={this.state.operatorsList}
editDataHandler={this.editData}
deleteDataHandler={this.deleteData}
/>
</div>
);
}
The components are remounted when I switch between the routes at the front end. To troubleshoot, I put logging in renderForm function and in the ActivityForm constructor. I can see the logging from renderForm only when the App component is mounted and not when I switch between routes. However, I can see the logging from ActivityForm constructor whenever I switch between the components at the front end using a navigation bar.
I lose all the states I had in the component because of this. The behavior I expect is that the component should not remount when I switch tabs. Can anyone please help?
React Router does basically update the UI according to a specific state. They handle that in a very organized manner with Browser Navigation events like Back, Refresh & so on.
As we all know, when React change the UI according to a specific updated state, it will mount & unMount relevant components accordingly. https://reacttraining.com/blog/mount-vs-render/ article explains that concept very nicely.
That same behavior will apply to React Router also. When you navigate to different path, existing components can be unMount & new components can be mount according to your code.
Route with render prop
return (
<div className="container">
<h1 className="text-center main-title">Activity Logger</h1>
<Router>
<NavigationBar />
<Router>
<Route path="/activitydisplay" render={() => this.renderTable()} />
<Route exact path="/" render={() => this.renderForm()} />
</Router>
</Router>
</div>
);
Note - It's better if you can pass the component into Route using render prop. That will make renderForm or renderTable to run before ActivityForm or ActivityDisplay mount. It will also make sure that to pass all the updated props correctly to the components where necessary.
Let me know if you need further support.
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.
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
I'm trying to create an app with Reactjs + Redux + React router but I'm having some problems that I don't understand what it's causing it. Most probably I'm not fully understand how it works.
When I update the store of Redux all the components get re rendered instead of the ones where the state it's used. That means that my api calls for example are running twice when I simple show a flash message. EG:
render() {
const user = this.props.user;
if( ! user || ! user.token ) {
return (<Redirect
to={{
pathname: "/login",
state: {from: this.props.location}
}}
/>)
}
return (
<div className="app">
<Header {...this.props} />
<FlashMessage message={this.props.flash.message} type={this.props.flash.msg_type} {...this.props}/>
<div className="app-body">
<Sidebar {...this.props}/>
<main className="main">
<Breadcrumb />
<Container fluid>
<Switch>
<Route path="/settings/permissions/add" name="Add Permission"
component={() => <AddPermissionView {...this.props}/>}/>
<Route path="/settings/permissions/" name="Permissions"
component={() => <ListPermissions {...this.props}/>}/>
<Route path="/dashboard" name="Dashboard"
component={() => <Dashboard {...this.props}/>}/>
<Route path="/logout" name="Logout" component={() => <Redirect to="/login"/>}/>
<Redirect from="/" to="/dashboard"/>
</Switch>
</Container>
</main>
<Aside />
</div>
<Footer />
</div>
);
}
So for example if I update the store for flash message, the Flashmessage compoenent gets rendered as it should, but also the Dashboard,sidebar,Header, etc.
I thought only the state that changed it's rendered again.
That means I need to use shouldComponentUpdate on every component I create to avoid that behaviour ?
Render Prop and Component Prop inside of Route.
When you use component instead of render the router uses React.createElement to create a new React element from the given component.
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.
Solution
Replace component props with the render prop in your routes.
<Route path="/settings/permissions/add" name="Add Permission" render={() => <AddPermissionView {...this.props}/>}/>
<Route path="/settings/permissions/" name="Permissions" render={() => <ListPermissions {...this.props}/>}/>
<Route path="/dashboard" name="Dashboard" render={() => <Dashboard {...this.props}/>}/>
<Route path="/logout" name="Logout" render={() => <Redirect to="/login"/>}/>
By default, updates to the Redux Store cause application-wide re-renders, regardless of where the update was applied in the Store. In case of small applications, this behavior is typically not noticeable. However, as an application (and its state tree) grows, its performance can be easily hampered by that blanket "re-render every component" strategy.
Fortunately, there are solutions to this problem. I use React Reselect, which helps create memoized selectors that help localize renders to components whose state was affected, leaving everything else unchanged:
https://github.com/reactjs/reselect
I'd suggest reading through the text on the page, perhaps twice, watching the video that's linked in the documentation, and actually doing an example project to understand how Redux works naturally, and how Reselect gets rid of unnecessary component re-renders via memoized selectors.
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