React component loops updating (GraphQL) - javascript

Good day!
I keep getting
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.
which looks obvious, but i fail to see the loop in my component.
ComponentWillUpdate() shows that it calls lots of rerender with the same props and state in a short amount of time.
Thanks in advance.
src/TitleList.js
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
const titlesToRender = this.props.allTitlesQuery.allTitles
this.setState({'items': titlesToRender})
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}

When you call this.setState it calls your renderagain. So if you call setState from render it goes to recursive loop.
You can try something as :-
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
componentDidMount () {
this.updateState(props);
}
componentWillReceiveProps (nextProps) {
if (this.props.allTitlesQuery.allTitles !== nextProps.allTitlesQuery.allTitles) {
this.setState(nextProps);
}
}
updateState (props) {
this.setState({"items":props.allTitlesQuery.allTitles});
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}
Use componentDidMount method to render the data for first time and if data changes update using componentWillReceiveProps method

the loop is caused by this.setState({'items': titlesToRender}) in your render function

You shouldn't be calling setState inside of render, do it in another lifecycle method like componentDidMount or componentWillReceiveProps:
Render shouldn't modify state: https://reactjs.org/docs/react-component.html#render

Related

componentDidUpdate - Error: Maximum update depth exceeded

class Suggestions extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
console.log(event.target.value)
}
componentDidUpdate(props, state) {
if(props.dataSearchBox) {
this.setState({visible: true})
} else {
this.setState({visible: false})
}
}
render() {
return (
<div className={`g${this.state.visible === true ? '': ' h'}`}>
<ul>
</ul>
</div>
);
}
}
export default Suggestions;
I am trying to set the state based on the props change, however I get the following error inside componentDidUpdate:
There was an 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.
What am I doing wring here?
Your problem is here:
componentDidUpdate(props, state) {
if(props.dataSearchBox) {
this.setState({visible: true})
} else {
this.setState({visible: false})
}
}
componentDidUpdate gets called, it sets the state, which triggers componentDidUpdate... round and round.
Do you need a separate state property to handle this? That's a bit of an anti-pattern - you have what you need already as a prop, just use its value to control the visibility directly:
render() {
return (
<div className={`g${this.props.dataSearchBox ? '': ' h'}`}>
<ul>
</ul>
</div>
);
}

React: Using `setState` with a function inside `componentWillReceiveProps`

Considering the following React Component snippet, I need to set a new component state based on the new props and the current state. It's preferred to use an "updater" function when defining new state which is based on the old one, now my confusion is that I have the nextProps parameter given to the componentWillReceiveProps lifecycle method, but the updater function also, gets a props parameter. Which one should I use?
Now I guess this should be specific to React 16.2; I'm guessing the introduction of the new getDerivedStateFromProps(nextProps, prevState) static method in React 16.3 should eliminate this confusion (right?)
class MyComponent extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
value: props.value,
};
}
componentWillReceiveProps(nextProps) {
this.setState((prevState, props) => {
return {
value: (/* `nextProps or props` */).value,
}
});
}
render() {
// ...
}
}
There are exactly the same thing. If your want to access previous props, the this.props remains untouched in componentWillReceiveProps method.
My intuition is that setState doesn't fire immediately in side componentWillReceiveProps.
Let's consider the example below.
After first click, the curProps will get 0, and props, nextProps both return 1.
class Test extends Component {
state = {
value: 0
};
componentWillReceiveProps(nextProps) {
const curProps = this.props;
this.setState((prevState, props) => {
console.log('[curProps]', curProps.num);
console.log('[props]', props.num);
console.log('[nextProps]', nextProps.num);
const value = prevState.value + nextProps.num;
return {
value
};
});
}
render() {
return <h4>{this.state.value}</h4>;
}
}
class App extends Component {
state = {
value: 0
};
render() {
return (
<div>
<Test num={this.state.value} />
<button onClick={this.onClick}>Add</button>
</div>
);
}
onClick = () => {
this.setState(prevState => ({ value: prevState.value + 1 }));
};
}
There is the codesandbox demo https://codesandbox.io/s/zxpovzkywx

TypeError: this.props.propName.map is not a function react js

I want to fetch JSON data, store it in state and then pass it to component through props. In the component I want to use the map function but it shows me this error :
TypeError: this.props.dataQueries.map is not a function.
This is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataQueries: ''
}
}
fetchData() {
fetch('https://jsonplaceholder.typicode.com/posts', {method: "GET"}).
then(res => res.json()).
then(result => this.setState({ dataQueries: result }));
}
componentWillMount() {
this.fetchData();
}
render() {
return (
<div>
<ShowPosts dataQueries={ this.state.dataQueries } />
</div>
);
}
}
And this is my component :
class ShowPosts extends Component {
render() {
return (
<div>
{
this.props.dataQueries.map((query, index) => {
return index;
})
}
</div>
);
}
}
Initially, you're setting dataQueries to ''. Which has no map, as it's a string. It's only an array later, when your fetchData async call has completed. Nothing prevents render from being called before the async call completes.
Initialize it to [] instead, or modify render to avoid trying to use it as an array when it's not an array.
You should call this.fetchData() inside componentDidMount() lifecycle method. So when component is mounted only then you update the state with response from API. Also, you should render ShowPosts component when there are posts to render using conditional rendering.
render() {
return (
<div>
{this.state.dataQueries.length && <ShowPosts dataQueries={ this.state.dataQueries } />}
</div>
);
}
And your initial dataQueries should be an empty array. dataQueries = []

React don't change state after first method trigger

In my React app i have components structure:
-AllElements
--SingleElement
--SingleElementDetails
I am passing method See to SingleElement component where I invoke seefunc to invoke see method from AllElements component. The problem i my state (name) in AllElements not change after first onClick trigger, it changes after secund click. Could you tell my why ?
class AllElements extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
viewingElement: {
name:""
}
}
this.see = this.see.bind(this);
console.log('Initial Sate',this.state.viewingElement);
}
see(name) {
this.setState({
viewingElement: {
name:name
}
});
console.log('State after SEE',this.state.viewingElement);
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
see={this.see}
/>
))}
<SingleElementDetails viewingElement={viewingElement}/>
</div>
);
}
}
class SingleElement extends Component {
constructor(props) {
super(props);
}
seefunc(name) {
this.props.see(this.props.name);
console.log('Name in seefunc props',this.props.name);
}
render() {
return (
<div onClick={this.seefunc.bind(this)}>
DIV CONTENT
</div>
)
}
}
The problem you have here is that setState is asynchronous. It does work the first time but you do not see it in your console.log because the console.log happens before the state is updated.
To see the updated state use the second argument of setState which is a callback function (https://reactjs.org/docs/react-component.html#setstate):
this.setState({
viewingElement: {
name:name
}
}, () => {
console.log('State after SEE',this.state.viewingElement);
});
And in SingleElement use the componentWillReceiveProps(nextprops) (https://reactjs.org/docs/react-component.html#componentwillreceiveprops) method from react lifecycle to see the updated props:
seefunc(name) {
this.props.see(this.props.name);
}
componentWillReceiveProps(nextprops) {
console.log('Name in props',nextProps.name);
}
It does change. However setState is an aync process so you're only logging the previous state to the console. setState does provide a callback that allows you to run code after the async process has finished, so you can do:
this.setState({
viewingElement: {
name:name
}
}, () => console.log('State after SEE',this.state.viewingElement));
DEMO

setState(...): Can only update a mounted or mounting component even if component is mounted

I am using react latest version, I receive the following error when click a button which run expandMenu()
Here tracing the lifecycle:
constructor
componentWillMount
render
componentDidMount
componentWillReceiveProps
render
componentWillReceiveProps
render
componentWillReceiveProps
render
expandMenu <<< click on the button - boom error!
AFAIK the component is mounted when running expandMenu(), which make the error to be ambiguous. What could cause the problem and how to fix it?
setState(...): Can only update a mounted or mounting component. This
usually means you called setState() on an unmounted
export class Menu extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
showPath: true,
selectedItemIdx: 1,
}
}
componentWillReceiveProps(nextProps: Props) {
const { items } = nextProps
this.setState({
selectedItemIdx: items.length - 1,
})
}
componentWillMount() {
console.debug('componentWillMount')
}
componentDidMount() {
console.debug('componentDidMount')
}
componentWillUnmount() {
console.debug('componentWillUnmount')
}
expandMenu = () => {
console.debug('expandMenu')
const { items } = this.props
if (items.length > 1) {
this.setState({ showPath: true }) // error here
}
}
itemClickHandler = (idx: number) => {
this.expandMenu()
}
render() {
console.debug('render')
const { classes } = this.props
return (
<div className={classes.root}>
<ButtonBase
className={classes.title}
onClick={() => {
this.itemClickHandler(idx)
}}
>
))}
</div>
)
}
}
Issue was related to react-hot-loader I had to remove it from my project dependencies.

Categories