Cannot update state from render method React - reactjs

I have a NavLink component like this:
<NavLink isActive={(match, location) => this.isActiveFunc(match, location)} className={classes.subLink} to={{ pathname: "/admin/users" }}>
<ListItem button className={classes.nested}>
<ListItemText disableTypography={this.state.activePath === "/admin/users"} inset primary="Users" />
</ListItem>
</NavLink>
and try to set my state every time the NavLink is active as follows:
updateActivePath = (match) => {
if (match && this.state.activePath !== match.path) {
this.setState({activePath: match.path})
}
};
isActiveFunc = (match, location) => {
this.updateActivePath(match);
};
This however gives me the following error:
Warning: Cannot update during an existing state transition (such as
within render or another component's constructor). Render methods
should be a pure function of props and state; constructor side-effects
are an anti-pattern, but can be moved to componentWillMount.
Any ideas how I can fix this?

If the "state" you're after is already in React Router, then copying it to your own state is unnecessary duplication, and the cause of your error (trying to update the state during render. You're better off just grabbing the state from the React Router. You can use a Route without a path for that
<Route render={({ location }) => (
// this will render whenever the location changes
<YourNavigationBar location={location} />
)}
/>

You use a setState in react lifecycle. You call your method updateActivePath in render or other lifecycle method react. This trigger an extra rendering for your component.
For fix this warning change your setState location in componentWillMount method or in function call by action (click, ...).
For must information go to lifecycle component documentation: https://reactjs.org/docs/react-component.html

Related

useEffect on child component fires every other click

I have a "Zones" component that has a "heatmapConfig" useState. This state is passed via props to a HeatmapConfiguration component, inside which I want to have a useEffect hook that detects when an option changes (the option is basically a checkbox to turn an option on and off).
The parent component is:
export const Zones = () => {
const [heatmapConfig, setHeatmapConfig] = useState({active: false});
return (
<HeatmapConfiguration
state={heatmapConfig} setState=setHeatmapConfig} />
<Map>
{heatmapConfig.active && <HeatmapLeaflet {...{ mapRef, heatmapData, heatmapConfig }} />}
</Map>
)
}
Then the child is passed through the component and it's as follows:
export const HeatmapLeaflet = ({ mapRef, heatmapData, heatmapConfig }) => {
useEffect(()=>{
console.log(heatmapConfig);
},[heatmapConfig]);
}
When I click to turn on the option it logs {active: true}, but nothing is logged when I turn it off.
If I place the useEffect hook in the parent component, it obviously works as intended, logging each time the option is on and off, I just don't know how to make it work on the child component. Having the useEffect in the parent won't be good in case you suggest that change, since with that tons of other stuff must change.
The Map component is a leaflet map container and I think the only part relevant to this case is:
export const Map = ({ children }) => {
return(
{children}
)
}
EDIT:
Within the component I have two others which are working as expected (PerHourScatter and StopsScatter):
<Map>
{scatterplotConfig.active && <PerHourScatter {...{ scatterplotData, scatterplotConfig }} />}
{stopsScatterConfig.active && <StopsScatter {...{ stopsScatterData, stopsScatterConfig }} />}
{heatmapConfig.active && <HeatmapLeaflet {...{ mapRef, heatmapData, heatmapConfig }} />}
</Map>
By the comments I realize the difference is that the HeatmapLeaflet component doesn't actually render, it returns emtpy like this: return <></>;
I may have to follow a different approach, and maybe create this component not as a component but as a custom hook?
How do I solve this?

Does react re render everything when we go to a new link?

I am currently creating a react app for practice. I am curious does react render everything when we go to a new link? For eg. These are my routers
<Route exact path="/" component={AuthenticatedUser(Books)}></Route>
<Route exact path="/librarians" component={AuthenticatedUser(Librarians)}></Route>
And my Higher Order Component AuthenticatedUser is as follows:
function AuthenticatedUser(Component) {
return function AuthenticatedComponent({ ...props }) {
const classes = useStyles();
return confirmUserAuthencition() ? (
<>
<SideMenu />
<Header />
<div className={classes.appMain}>
<PageHeader></PageHeader>
<Component {...props}></Component>
</div>
</>
) : (
<Redirect to="/login" />
);
};
}
I am just curious, when I go from "/" link to "/librarians", do components SideMenu and Header rerender?
React re-renders on state change.
From: https://www.educative.io/edpresso/how-to-force-a-react-component-to-re-render
React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
These changes can come from setState, useState, and/or forceUpdate calls
It depends on the element that redirects you to the new link. If you use react router's <Link to="/librarians"> then no, React will not re-render. However, if you use the standard html <a href="/librarians"> then it will.
No, if you're moving from / to /librarians path, your <SideMenu /> and <Header /> won't re-render. React uses virtual DOM to do the updates on actual DOM (virtual DOM is a copy of the actual DOM and it can do the updates without affecting actual DOM)
During reconcilation process, react compares virtual and actual dom and then do the updates on actual dom based on the nodes that are changed.
In your case, since you're not completely removing AuthenticatedUser component when redirection, it won't re-render <SideMenu /> and <Header /> components that are included in AuthenticatedUser component as childs. But AuthenticatedUser re-render itself since you're changing the passed Component prop.
In order identify this properly you can put a console.log in <SideMenu /> and <Header /> to check whether re-render themselves when moving from / to /librarians.
Since your HOC's return statement depends on the value of the confirmUserAuthencition(), we can't always say whether or not the and components will get re-rendered.
The DOM will stay unaffected as long as the user remains authenticated or unauthenticated. The two components need not be re-rendered each time this route is hit in this case.
React won't re-render the entire page unnecessarily. It will only re-render all components except the SideMenu and Header component.
You may find this article helpful in understanding how react re-renders - Article
It will re-render any component that has changed, as determined by shouldComponentUpdate() for each component
In your case, if you're navigating to the new page via menu navigation, it will re-render the final component, as well as the nav-menu. Depending on your implementation, it's quite likely that the it will re-render the whole AuthenticatedUser component.
Here's the component lifecycle docs:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate

Highlight "antd" menu-item when route is changed from redux-saga

I am using the "antd" framework for a react app and have one slight issues with menus, more precisely with highlighting the currently selected menu item.
I found the following solution that works fine when a link is called, an url is entered directly to a specific page and when "back" is pressed:
render() {
const href = window.location.href.split('/');
const href2 = href[3];
<Menu
mode="horizontal"
theme="dark"
defaultSelectedKeys={['/' + href2]}
selectedKeys={['/' + href2]}
>
<Menu.Item key="/">
...
</Menu.Item>
<Menu.Item key="/test">
...
</Menu.Item>
<Menu>
My problem happens when I set my route from a redux saga (with history.push), I can then see that the "navigation bar" component gets rendered/updated before the "history.push" action is called in the saga.
How can I get my "navigation bar" component to be re-rendered after every route change (however the route change is done). My "navigation bar" is currently a component, because I tried to use the different events, but none of them gets fired. It could also be a functional component (it has no state) it that helps.
I also tried suggestions from "https://stackoverflow.com/questions/41054657/react-routerantd-how-to-highlight-a-menu-item-when-press-back-forward-button" but could not get it to work with my use case
If you're using react-router library you shouldn't use window.location object. In your example of code, you're using the class component. In this case the component in <Route> receives location prop. And you can use it when you want
class Comp extends React.Component {
componentDidUpdate(prevProps) {
// will be true
const locationChanged =
this.props.location !== prevProps.location;
}
}
<Route component={Comp} />;

React component seems to rerender because of child components (but shouldn't)

I have a page with a tabbed view. If tab 1 is active it should show component A, otherwise component B.
Based on which page/route I come from before, it should default to component B. Both the components don't need props (as they handle data/state internally).
Problem: The parent component renders three times. Because of this, I loose the state value from useLocation and can't display the second tab. Simplified parent component code:
function ContractOverview() {
const location = useLocation();
console.log(location);
const showBillingCycleTab = location.state?.selectedTab === 'billing-cycles';
return (
<Container>
<h1>
<Trans i18nKey="navigation.billing" />
</h1>
{showBillingCycleTab ? <BillingCycleTable /> : <ContractTable />} // if I do it like this, ContractOverview renders three times (and I loose location state)
{showBillingCycleTab ? <p>component A</p> : <p>component B</p>} // if I do it like this, ContractOverview renders only once and the location state is correct.
</Container>
);
}
The route is set up like this:
<Route path="/contracts" component={ContractOverview} exact />
Screenshot of the location logs:
Usually I'd expect the parent component to only render once as there are no props that could change nor component state that could do something. What am I doing wrong?

Link changing URL but not the page

I am using Ant design breadcrumbs. I am trying to change the page using link and pushing the URL, I can see the URL change but the page in not changing.
I tried using Link, then creating a function for onClick but everything just change the URL.
Route:
<Route exact path="/assistant/:wId/skill/xyz/:sid" component={ xyz } />
Tried process 1:
<Breadcrumb separator=">">
<Breadcrumb.Item
onClick={this.redirectToParam2}>
{param2}
</Breadcrumb.Item>
</Breadcrumb>
redirectToParam2 = () => {
this.props.history.push(`/assistant/${wId}/skill/xyz/${sId}`);
}
Tried process 2:
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to= {`/assistant/${wId}/skill/xyz/${sId}`}>
{param2}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
Even I tried without the Breadcrumbs component but it's still not changing the page.
I want the page to change as soon as the URL changes.
Thank you in advance.
Try this,
import { Link } from "react-router-dom";
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to= {`/assistant/${wId}/skill/xyz/${sId}`}>
{param2}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
The problem you are running into, is that with changing the parameters used as props for the xyz component, the component is not replaced but gets new properties. Since nothing changes, i'm assuming you have state that gets filled either in the constructor or ComponentWillMount/ComponentDidMount.
React class components have a lifecycle function for this: componentDidUpdate.
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
Quote from react docs, See: https://reactjs.org/docs/react-component.html#componentdidupdate
componentDidUpdate(prevProps) {
if ((this.props.params.match.sid !== prevProps.params.match.sid) ||
(this.props.params.match.wid !== prevProps.params.match.wid)) {
this.populateState(this.props.params.match); //fill your state
}
}

Resources