react: how to set state on page render - javascript

I'm working with react and I'm trying to set state on page render but it keeps throwing the below error.
---Error----
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
---Error----
Below is my code of the sidebar component. I'm using Context API for data management as shown in the code and I'm trying to set state of the role inside the showContext method with the value I'm getting through the Context API Consumer.
import React, { Component, PropTypes } from "react";
import { Menu, Icon } from "antd";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import AdminPage from "../components/Admin/AdminPage";
import App from "../App";
import "../components/Login/LoginPage";
import { LoginContext } from "../context/LoginContext";
export default class MainMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
roleValue: "admin",
status: false
};
this.showContext = this.showContext.bind(this);
}
showContext(context) {
let role = context;
if (this.state.roleValue == role) {
this.setState({
roleValue : "admin",
});
}
}
render() {
if (window.location.href == "http://localhost:8081/") {
var loginHeader =
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
selectedKeys={[location.pathname]}
>
{this.props.children}
<Menu.Item key="mastering">Mastering</Menu.Item>
</Menu>;
}
else {
if (this.state.roleValue == "general") {
var generalHeader1 =
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
selectedKeys={[location.pathname]}
>
{this.props.children}
<Menu.Item key="mastering">Mastering</Menu.Item>
<Menu.Item>
<Link to="/"> Logout</Link>
</Menu.Item>
<Menu.Item>
<Link to="/home">Home</Link>
</Menu.Item>
</Menu>;
}
else {
var generalHeader2 =
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
selectedKeys={[location.pathname]}
>
{this.props.children}
<Menu.Item key="mastering">Mastering</Menu.Item>
<Menu.Item>
<Link to="/"> Logout</Link>
</Menu.Item>
<Menu.Item>
<Link to="/admin">Admin</Link>
</Menu.Item>
<Menu.Item>
<Link to="/home">Home</Link>
</Menu.Item>
</Menu>;
}
}
return (
<div>
{loginHeader}
{generalHeader1}
{generalHeader2}
<LoginContext.Consumer>
{(context) => (
this.showContext(context.state.role)
)
}
</LoginContext.Consumer>
</div>
);
}
}

setState() causes a call to render(). So if you call setState() in render() you will get infinite recursion. DON'T DO THIS. Instead find the correct way to do what you want within the framework defined by React and the other libraries you use.

A React component waits for any change on its own state and props in a normal conditions and when a changes takes place, it calls Render method and expect a component or null to be returned.
What you did is: Component starts and tries to do its very first render and you set component states which request another render and it set states again and it goes on like this. It calls each other circularly.
Set state outside of Render method to avoid this situation.

You are doing it in wrong way, as you are setting the state inside the render the render loop will be infinite.
Please check this link which will help you to resolve your problem, and still if you face any issue after implementation let me know I will love to help you.
Reference link : https://reactjs.org/docs/context.html#dynamic-context

Use React lifecycle method componentDidMount to handle such tasks related to setState.

In you are code you have written code in your render which is not good practices. Please remove code from render and add it into componentDidMount. Use componentDidMount() lifecycle which will make your life easy the code should be something like below.
componentDidMount() {
this.showContext(this.state.roleValue)
}
As I have seen you have different side bar for each user. The best thing you can do in this create separate component for each role for sidebar and show that particular component as role wise.

Related

React lifecycle methods not trigger, is there some hack behind lifecycle methods?

Today I found something like which I was not expected. This does not happen in Angular or maybe another JS library/framework. But today I was shocked when React lifecycle methods not triggered. Is there some hack behind it. let's see the code.
I have a component A
import React, { Component } from 'react'
class A extends Component {
componentDidMount() {
console.log('component mount', this.props.name);
}
render() {
return (
<>
Hello - {this.props.name}
</>
)
}
}
export default A;
I initialized that component twice in App component with some conditions:
import React, { Component } from 'react';
import { render } from 'react-dom';
import A from './A';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
isShow: true
};
this.setIsShow = this.setIsShow.bind(this);
}
setIsShow() {
this.setState(() => {
return {isShow: !this.state.isShow};
});
}
render() {
return (
<div>
<button onClick={this.setIsShow}>Show A1</button>
<button onClick={this.setIsShow}>Show A2</button>
<br />
<br />
{this.state.isShow ? <A name="A1" />
:
<A name="A2" />
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Now, what is expected behavior? name props value changed when I clicked on buttons. that's expected, alright. But there is no lifecycle method called when component reinitialized :(. The componentDidMount fire only once.
Now add key property in A component selector and you will see componentDidMount called on every time whenever A component reinitialized.
{
this.state.isShow ? <A name="A1" key="1" />
:
<A name="A2" key="2" />
}
This is the expected behavior. But the question is why not without key property.
Checkout Online demo: https://stackblitz.com/edit/problem-react-lifecycle
What is happening here is react is optimizing the rendering for you. The same component is rendered with an updated prop vs unmounting and remounting with a different prop.
The lifecycle events are running just fine, you are just not seeing your console.log because componentDidMount is only fired on mount. If you use another lifecycle event like componentDidUpdate you will notice it fires on each change.
componentDidUpdate() {
console.log("component update", this.props.name);
}
See a live example here
When you don't specify a key react will add one internally... if the component is the same then the same key would be generated for that component meaning it wont be unmounted. There is no need to undergo the extra overhead of unmounting and remounting essentially.
However, specifying a different key for the component forces the unmount and remount because to react you've told it that these should be treated as completely different components
Edited:
This is because React is optimizing to not rerender the entire component. Adding another lifecycle method will show the expected output in the console
componentDidUpdate() {
console.log('component update', this.props.name);
}

React Routing to Same Component with Different URL

In my main class I have a nav bar with the options below:
<NavDropdown title="Search" id="collasible-nav-dropdown">
<NavDropdown.Item href="#/searchpage/p" onClick={this.dontEdit}>Find People</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/s" onClick={this.searchSchool}>Find Schools</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/w" onClick={this.dontEdit}>Find Work Places</NavDropdown.Item>
</NavDropdown>
These have a route which routes to the same component which then reads the parameter at the end of the URL and runs a different search depending on the value. For example 's' is search schools and 'p' is search people. If I navigate between the different search functions from the nav bar then it doesn't refresh to the new search. For example if I go from 'Find Schools' to 'Find Work' it stays on schools, but if I were to go direct to 'Find Work Places' then it goes there direct. Also if I navigate to the home page and back to another search then it works.
The route looks like:
<Route path="/searchpage/:type" render={props => (<SearchPage {...props} findPerson={this.findPerson} routeReset={this.routeReset} getPersonsByName={this.getPersonsByName} />)}/>
Is anyone able to advise how to get this to route as I want it to? The search component is like:
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Flash from './components/flash';
import Search from "./components/search";
const searchtypes = {"p":"People","w":"Work Places","s":"Schools"};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
type:this.props.match.params.type
}
}
componentDidMount(){
}
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.state.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.state.type}/></Col></Row>
</Container>
);
}
}
export default SearchPage;
The Route's render prop doesn't remount when the matched route doesn't change, i.e. even when the route matches but the route param is different it won't re-render. Instead use the component prop.
react-router-dom component
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 (below).
<Route
path="/searchpage/:type"
component={props => (
<SearchPage
{...props}
findPerson={this.findPerson}
routeReset={this.routeReset}
getPersonsByName={this.getPersonsByName}
/>
)}
/>
An alternative to this is to implement the componentDidUpdate lifecycle function in SearchPage to detect when the route param prop updates and update the type stored in state. This way the component won't continually unmount/mount each time.
componentDidUpdate(prevProps) {
if (prevProps.match.params.type !== this.props.match.params.type) {
setState({
type: this.props.match.params.type,
});
}
}
Try this:
class SearchPage extends Component {
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.props.match.params.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.props.match.params.type}/></Col></Row>
</Container>
);
}
}
The type data comes from props and therefore you should not persist it on the component state.
Note: Make sure you use react-router Link component it seems you use native a tags

How to hide/display separate forms using buttons and redux?

I'm new to react and redux (and posting on stack overflow!).
I'd like to hide/display a redux-form based on a button choice.
I have two buttons: Option-A and Option-B.
I followed the redux tutorial exactly to have their onClick methods dispatch setVisibilityFilter(buttonprops.filter) through a container. See: FilterLink.js This works fine and updates the state's visibilityFilter with the corresponding option.
However, I'm stuck about how I should access the state's filter to hide/display different forms. I would like something similar to what formValueSelector does, but it isn't applicable for buttons (because they don't return values?)
This is my main component's code:
class MainForm extends Component {
render() {
const { error } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
/* If SHOW_A, display FORM_A, else if SHOW_B, display FORM_B */
</Grid.Column>
)
}}
I feel like just toying with the state directly now would waste the effort of implementing redux. I think I should be passing the value as a prop down to the child forms, but I'm confused how to do so, especially because I don't know how I would get that value without changing my onClick anyway, and onClick is already defined in FilterLink.js
There must be some way to access my state visibility filter to hide/display a form, just unsure how to get there. Thank you!
With connect, you can pass anything from the Redux Store to your component through its props.
So based on the link you posted, this should work:
import { connect } from 'react-redux'
class MainForm extends Component {
render() {
const { error, visibilityFilter } = this.props
return (
<Grid.Column width={9}>
<Button.Group floated='right'>
<FilterLink filter={VisibilityFilters.SHOW_A}>A</FilterLink>
<Button.Or />
<FilterForm filter={VisibilityFilters.SHOW_B}>B</FilterLink>
</Button.Group>
{visibilityFilter === VisibilityFilters.SHOW_A
? <FormA />
: <FormB />
}
</Grid.Column>
)
}}
const mapStateToProps = state => ({
visibilityFilter: state.visibilityFilter
})
export default connect(mapStateToProps)(MainForm)
Make sure you have connected the component you want to conditionally render things to the redux store.
import { connect } from 'react-redux'
...
const mapStateToProps = state => ({visibleFilter: state.visibilityFilter})
export default connect(mapStateToProps)(MainForm)
Then you can access this information in your connected component's props, e.g.
render() {
return {
{this.props.visibleFilter === VisibilityFilters.SHOW_A && (<FormA /> )}
{this.props.visibleFilter === VisibilityFilters.SHOW_B && (<FormB /> )}
}
}

React setState odd behaviour

New to React, in the following code I am passing data between two components via the parent. Flow is from Search to the parent App then to another child Sidebar. I am able to send to both from Search to App and App to Sidebar individually but for some reason setState is not behaving as expected making the link to trigger a refresh of <Search updateMenu={this.handleSearchResult} /> as you can see in the console.log code comments below:
import React, { Component } from 'react';
import './App.css';
import Search from './Search';
import Sidebar from './Sidebar';
class App extends Component {
constructor() {
super()
this.state = {
menu: []
}
}
handleSearchResult = (array) => {
// always the correct value
console.log('in ', array);
this.setState( {menu: menuList})
// 1st call : empty
// 2nd call : previous value not showing on 1st call + new value as expected
// does not trigger <Sidebar list={this.state.menu}/>
console.log('out', this.state.menu);
}
render() {
return (
<div className="App">
// not refreshing
<Search updateMenu={this.handleSearchResult} />
<Sidebar list={this.state.menu}/>
</div>
);
}
}
export default App;
Logging this.setState(). Is not so straight forward. this.setState() is asynchronus.
Here is a reference on Medium.

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

Categories