I'm writing a script which moves dropdown below or above input depending on height of dropdown and position of the input on the screen. Also I want to set modifier to dropdown according to its direction.
But using setState
inside of the componentDidUpdate
creates an infinite loop(which is obvious)
I've found a solution in using getDOMNode
and setting classname to dropdown directly, but i feel that there should be a better solution using React tools. Can anybody help me?
Here is a part of working code with getDOMNode
(i
a little bit neglected positioning logic to simplify code)
let SearchDropdown = React.createClass({
componentDidUpdate(params) {
let el = this.getDOMNode();
el.classList.remove('dropDown-top');
if(needToMoveOnTop(el)) {
el.top = newTopValue;
el.right = newRightValue;
el.classList.add('dropDown-top');
}
},
render() {
let dataFeed = this.props.dataFeed;
return (
<DropDown >
{dataFeed.map((data, i) => {
return (<DropDownRow key={response.symbol} data={data}/>);
})}
</DropDown>
);
}
});
and here is code with setstate (which creates an infinite loop)
let SearchDropdown = React.createClass({
getInitialState() {
return {
top: false
};
},
componentDidUpdate(params) {
let el = this.getDOMNode();
if (this.state.top) {
this.setState({top: false});
}
if(needToMoveOnTop(el)) {
el.top = newTopValue;
el.right = newRightValue;
if (!this.state.top) {
this.setState({top: true});
}
}
},
render() {
let dataFeed = this.props.dataFeed;
let class = cx({'dropDown-top' : this.state.top});
return (
<DropDown className={class} >
{dataFeed.map((data, i) => {
return (<DropDownRow key={response.symbol} data={data}/>);
})}
</DropDown>
);
}
});
This question is related to
javascript
reactjs
ecmascript-6
I had a similar problem where i have to center the toolTip. React setState in componentDidUpdate did put me in infinite loop, i tried condition it worked. But i found using in ref callback gave me simpler and clean solution, if you use inline function for ref callback you will face the null problem for every component update. So use function reference in ref callback and set the state there, which will initiate the re-render
I would say that you need to check if the state already has the same value you are trying to set. If it's the same, there is no point to set state again for the same value.
Make sure to set your state like this:
let top = newValue /*true or false*/
if(top !== this.state.top){
this.setState({top});
}
this.setState creates an infinite loop when used in ComponentDidUpdate when there is no break condition in the loop. You can use redux to set a variable true in the if statement and then in the condition set the variable false then it will work.
Something like this.
if(this.props.route.params.resetFields){
this.props.route.params.resetFields = false;
this.setState({broadcastMembersCount: 0,isLinkAttached: false,attachedAffiliatedLink:false,affilatedText: 'add your affiliate link'});
this.resetSelectedContactAndGroups();
this.hideNext = false;
this.initialValue_1 = 140;
this.initialValue_2 = 140;
this.height = 20
}
This example will help you to understand the React Life Cycle Hooks.
You can setState
in getDerivedStateFromProps
method i.e. static
and trigger the method after props change in componentDidUpdate
.
In componentDidUpdate
you will get 3rd param which returns from getSnapshotBeforeUpdate
.
You can check this codesandbox link
// Child component_x000D_
class Child extends React.Component {_x000D_
// First thing called when component loaded_x000D_
constructor(props) {_x000D_
console.log("constructor");_x000D_
super(props);_x000D_
this.state = {_x000D_
value: this.props.value,_x000D_
color: "green"_x000D_
};_x000D_
}_x000D_
_x000D_
// static method_x000D_
// dont have access of 'this'_x000D_
// return object will update the state_x000D_
static getDerivedStateFromProps(props, state) {_x000D_
console.log("getDerivedStateFromProps");_x000D_
return {_x000D_
value: props.value,_x000D_
color: props.value % 2 === 0 ? "green" : "red"_x000D_
};_x000D_
}_x000D_
_x000D_
// skip render if return false_x000D_
shouldComponentUpdate(nextProps, nextState) {_x000D_
console.log("shouldComponentUpdate");_x000D_
// return nextState.color !== this.state.color;_x000D_
return true;_x000D_
}_x000D_
_x000D_
// In between before real DOM updates (pre-commit)_x000D_
// has access of 'this'_x000D_
// return object will be captured in componentDidUpdate_x000D_
getSnapshotBeforeUpdate(prevProps, prevState) {_x000D_
console.log("getSnapshotBeforeUpdate");_x000D_
return { oldValue: prevState.value };_x000D_
}_x000D_
_x000D_
// Calls after component updated_x000D_
// has access of previous state and props with snapshot_x000D_
// Can call methods here_x000D_
// setState inside this will cause infinite loop_x000D_
componentDidUpdate(prevProps, prevState, snapshot) {_x000D_
console.log("componentDidUpdate: ", prevProps, prevState, snapshot);_x000D_
}_x000D_
_x000D_
static getDerivedStateFromError(error) {_x000D_
console.log("getDerivedStateFromError");_x000D_
return { hasError: true };_x000D_
}_x000D_
_x000D_
componentDidCatch(error, info) {_x000D_
console.log("componentDidCatch: ", error, info);_x000D_
}_x000D_
_x000D_
// After component mount_x000D_
// Good place to start AJAX call and initial state_x000D_
componentDidMount() {_x000D_
console.log("componentDidMount");_x000D_
this.makeAjaxCall();_x000D_
}_x000D_
_x000D_
makeAjaxCall() {_x000D_
console.log("makeAjaxCall");_x000D_
}_x000D_
_x000D_
onClick() {_x000D_
console.log("state: ", this.state);_x000D_
}_x000D_
_x000D_
render() {_x000D_
return (_x000D_
<div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>_x000D_
<p style={{ color: this.state.color }}>Color: {this.state.color}</p>_x000D_
<button onClick={() => this.onClick()}>{this.props.value}</button>_x000D_
</div>_x000D_
);_x000D_
}_x000D_
}_x000D_
_x000D_
// Parent component_x000D_
class Parent extends React.Component {_x000D_
constructor(props) {_x000D_
super(props);_x000D_
this.state = { value: 1 };_x000D_
_x000D_
this.tick = () => {_x000D_
this.setState({_x000D_
date: new Date(),_x000D_
value: this.state.value + 1_x000D_
});_x000D_
};_x000D_
}_x000D_
_x000D_
componentDidMount() {_x000D_
setTimeout(this.tick, 2000);_x000D_
}_x000D_
_x000D_
render() {_x000D_
return (_x000D_
<div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>_x000D_
<p>Parent</p>_x000D_
<Child value={this.state.value} />_x000D_
</div>_x000D_
);_x000D_
}_x000D_
}_x000D_
_x000D_
function App() {_x000D_
return (_x000D_
<React.Fragment>_x000D_
<Parent />_x000D_
</React.Fragment>_x000D_
);_x000D_
}_x000D_
_x000D_
const rootElement = document.getElementById("root");_x000D_
ReactDOM.render(<App />, rootElement);
_x000D_
<div id="root"></div>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
_x000D_
The componentDidUpdate
signature is void::componentDidUpdate(previousProps, previousState)
. With this you will be able to test which props/state are dirty and call setState
accordingly.
componentDidUpdate(previousProps, previousState) {
if (previousProps.data !== this.props.data) {
this.setState({/*....*/})
}
}
You can use setState inside componentDidUpdate
If you use setState
inside componentDidUpdate
it updates the component, resulting in a call to componentDidUpdate
which subsequently calls setState
again resulting in the infinite loop. You should conditionally call setState
and ensure that the condition violating the call occurs eventually e.g:
componentDidUpdate: function() {
if (condition) {
this.setState({..})
} else {
//do something else
}
}
In case you are only updating the component by sending props to it(it is not being updated by setState, except for the case inside componentDidUpdate), you can call setState
inside componentWillReceiveProps
instead of componentDidUpdate
.
Source: Stackoverflow.com