maximum call stack error using setState in render function - reactjs

I'm starting to learn it but can't find a solution for this -> I have an input and when the value exceeds 20 characters I want a tooltip to appear with the full value as is typed. I have it all built and kind of working. Problem is that I get a maximum call stack error because the state is being changed on every key press - I'm not sure of the best/correct way to go about it... any help would be greatly appreciated
See my code below and here is the pen
console.clear();
class Input extends React.Component {
render(){
return(
<div>
<input
className="main-input"
onChange={this.props.onChange}
placeholder={"Tell me something"}
/>
</div>
)
};
}
class Tooltip extends React.Component {
render(){
return(
<div
className="tooltip"
data-status={this.props.isShowing}>
<p>{this.props.TooltipValue}</p>
</div>
)
}
}
class App extends React.Component {
constructor(){
super();
this.state = {
message: '',
isShowing: false
}
}
onChange(e) {
this.setState({
message: e.target.value
});
}
render(){
const inputVal = (this.state.message.length);
if(inputVal >= 20){
this.setState({isShowing: true})
}
// else {
// this.setState({isShowing: false})
// }
return(
<div className="container">
<Tooltip
TooltipValue={this.state.message}
isShowing={this.state.isShowing}
/>
<Input onChange={this.onChange.bind(this)}/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('Main')
)

Why maximum call stack error when using setState in render function ?
Because when we do setState, React trigger the re-rendering of the component, if you do setstate inside render then after setState, component will render again, again it will find setState again render, it will become a infinite loop, So never do any setState inside render method.
Check this answer for more details about setState behaviour.
Instead of putting that check inside render method, put that inside onChange method, like this:
onChange(e) {
const inputVal = e.target.value;
this.setState({
message: inputVal,
isShowing : inputVal.length > 20 ? true : false
});
}
And remove these lines:
if(inputVal >= 20){
this.setState({isShowing: true})
}
// else {
// this.setState({isShowing: false})
// }
Or since the showing of Tooltip depends on the value of input element, you can avoid extra variable and directly check the length of input element with Tooltip property isShowing, like this:
<Tooltip
TooltipValue={this.state.message}
isShowing={this.state.message.length >= 20}
/>

No need to use a separate state for the tooltip since it already depends on the state value you can do it like this
<Tooltip
TooltipValue={this.state.message}
isShowing={this.state.message.length > 20} >
Also since you are using setState in the render() you are getting an error because setState triggers a re-render and thus beginning an infine loop as soon as you if-condition becomes true.
CODEPEN

Related

onChange not working in React for input box

I have a input box where I am showing the value if there is any fetched from the database and the input text box is editable. However, this input box doesn't reflect the changes on key press.
Here is the necessary following code:
constructor(props) {
super(props)
const { minorModel } = this.props;
this.state =
{
galleryModel: minorModel.galleryModel,
selectedIndex: 0
}
}
componentDidUpdate() {
this.props.minorModel.galleryModel = this.state.galleryModel;
}
onInputChange=(e)=>{
this.setState({
[e.target.name]:e.target.value
})
}
{this.state.galleryModel.photos.length > 0 &&
<PtInput type="text" label="Comment"
value={this.state.galleryModel.Comments[this.state.selectedIndex] === null ?
"Not Available " :
this.state.galleryModel.Comments[this.state.selectedIndex]}
onChange={this.onInputChange}
name={`${"Comment "+[this.state.selectedIndex]}`}
disabled={this.props.disabled}
/>
}
I probably think the problem is because I am using a data model which is passed as props in the constructor and the values in that data model is not changing so the new value is not reflected. Is that so? Any help is really appreciated to solve this issue.I also have a componentDidUpdate to reflect the changes.
There is no connection between the value attribute and your onChange handler, since they aren't relying on the same state, thus you have an uncontrolled input.
When the component mounts, you can load your data into the same state that is used to control the input.
Here is a simplified potential implementation:
class App extends React.Component {
constructor() {
super();
this.state = {
"pics": ['https://placehold.co/200x100', 'https://placehold.co/200x100'],
"comments": [["comment 1 pic 1", "comment 2 pic 1"], ["comment 1 pic 2", "comment 2 pic 2"]],
}
}
render() {
return (
<div>
{this.state.pics.map((pic, i) =>
<Picture pic={pic} comments={this.state.comments[i]} />)}
</div>
)
}
}
const Picture = ({ pic, comments }) => {
return <div>
<img src={pic} />
{comments.map(comment => <Comment comment={comment} />)}
</div>
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = { value: this.props.comment };
this.handleChange = this.handleChange.bind(this);
}
componentDidUpdate() {
// do stuff when Comment component updates
}
handleChange(e) {
this.setState({ value: e.target.value });
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Also, you shouldn't be updating props inside a component. They are meant to be immutable and top-down.
this.state.galleryModel.Comments[this.state.selectedIndex] is not the same state as this.state[${"Comment "+[this.state.selectedIndex]}]
So you are feeding the onChange value into state that you are not passing into the value prop on PtInput
to get things working properly, you need to decide which format you want to go with. You could resolve the galleryModel.Comments whenever minorModel updates to these indices ${"Comment "+[this.state.selectedIndex]} on your state, or work within the data model you have in galleryModel.Comments.
I'd suggest you consider why you need two sources of the same information.

Is there any public react function that gets executed every time state is changed just like render function?

I have a child component. It should create an object from props and render it. This object should get added as a state.
Below is the current code.
Example:-
<popupComponent element={object} />
popupComponent.js
class popupComponent extends Component {constructor(props) {
super(props);
this.state = {
name: ""
}
}
updateName (event) {
this.setState({
name: event.currentTarget.value
})
}
publishElement () {
this.props.saveAndClose({
name: this.state.name
});
this.setState({
name: ""
})
}
render() {
return (
<div draggable="true" >
<h4>Name:</h4>
<input id="elementName" type="text" placeholder="Enter element name" value={element.name} onChange={this.updateName.bind(this)}/>
<button id="saveAndClose" onClick={this.publishElement.bind(this)}>Save & close</button>
</div>
);
}
}
export default popupComponent;
Question: Which function other than render gets executed whenever state is changed? In this scenario constructor runs only once and I cannot try that because the time constructor gets executed, state isnt available.
Resolved issue by conditionally not creating the component at all.
Actual issue, Somehow this component's constructor was getting called only once but I wanted it getting called whenever it gets visually shown.
Resolved issue by conditionally not including the component at all as below.
{this.state.show ? <PopupMarkupEditor
element = {selectedElement}
saveAndClose = {this.saveElement}
show = {this.state.show}
/> : null }

google-maps-react infoWindow onChange issues

How to add a button in infowindow with google-maps-react?
Hello, I'm writing a React app, I was having an issue with changing state inside the InfoWindow from google-maps-react, the solution above helped me get through that hurdle.
Right now however, I'm having an issue with wanting to edit the content inside my InfoWindowEx component. Using the method above I am able to change the state of a text box inside the InfoWindowEx, however, when I click on the text box and I type it will let me type 1 letter and then I will have to click the text box again if I want to type the next letter, etc. I think this issue has to do with state.
I don't know if there is a solution to this, I have been trying a lot of different things, but hopefully someone can help me know what is going on.
Here is my InfoWindowEx component:
<InfoWindowEx
key={currentInfoWindow.id}
id={currentInfoWindow.id}
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
selectedPlace={this.state.selectedPlace}
onInfoWindowClose={this.onInfoWindowClose}
>
<div >
{infoWindowEditBoxes}
{infoWindowContent}
</div>
</InfoWindowEx>
the Edit boxes are rendering in conditional statements here are they:
if (this.state.editButton) {
infoWindowEditBoxes = (
<div>
<input key={this.props.marker} id="editedName" type="text" placeholder="New Bathroom Name" onChange={this.handleTextBoxState}></input>
<input key={this.props.marker} id="editedLocationName" type="text" placeholder="New Bathroom Location" onChange={this.handleTextBoxState}></input>
<button onClick={() => this.handleSubmitChangesButtonState()}>Submit Changes</button>
</div>
);
}
else {
infoWindowEditBoxes = null
}
and here is my state change function:
handleTextBoxState = (evt) => {
const stateToChange = {}
stateToChange[evt.target.id] = evt.target.value
this.setState(stateToChange)
console.log(stateToChange)
}
Thanks in advance!
I believe component state is getting updated properly in your example, apparently this behavior is related with InfoWindowEx component itself. The way how it is implemented, setState() causes to a re-render InfoWindow component which leads to losing input focus.
You could consider the following updated version of component which prevents re-rendering of info window if it has been already opened:
export default class InfoWindowEx extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.infoWindowRef = React.createRef();
this.containerElement = document.createElement(`div`);
}
componentDidUpdate(prevProps) {
if (this.props.children !== prevProps.children) {
ReactDOM.render(
React.Children.only(this.props.children),
this.containerElement
);
this.infoWindowRef.current.infowindow.setContent(this.containerElement);
this.setState({
isOpen: true
});
}
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.isOpen) {
return this.props.marker.position.toString() !== nextProps.marker.position.toString();
}
return true;
}
infoWindowClose(){
this.setState({
isOpen: false
});
}
render() {
return <InfoWindow onClose={this.infoWindowClose.bind(this)} ref={this.infoWindowRef} {...this.props} />;
}
}
Demo

Using client side search in react

Search function is working perfectly fine in the console log but when I try to assign that value to rows which is a state. So I setState the rows inside the setState in searchHandler. I know I'm making a mistake but I don't know how to rectify it. OMIT THE UNDECLARED STATES, MINIFIED THE CODE TO WHAT'S NEEDED
function searchingFor(searchingTerm) {
return function(x){
// console.log("searching",x);
return x.name.toLowerCase().includes(searchingTerm.toLowerCase())|| false;
}
}
class Main extends React.Component{
componentWillMount(){
this.props.fetchTopicsTableContent(this.state.sortBy,'ASC',0,this.props.match.params.CategoryName).then(result=> (this.setState({rows:result.payload.data})))
this.props.countTableContent(this.props.match.params.CategoryName).then(result=>(this.setState({count:result.payload})));
}
constructor(props){
super(props);
this.state={
rows:"",
searchTerm:"",
items:""
}
}
onSubmit(values){
values.preventDefault();
}
onSearchHandler(e){
this.setState({searchTerm:e.target.value},()=>{
{this.state.rows.filter(searchingFor(this.state.searchTerm)).map(item=>{
console.log(item);
//this.setState({rows:item})
})}
})
}
render(){
return(
<div>
<h3>Topics</h3>
<hr/>
<div>
<form onSubmit={this.onSubmit.bind(this)}>
<input type="text"
className="searchBar"
value={this.state.searchTerm}
onChange={this.onSearchHandler.bind(this)}
/>
</form>
</div>
</div>
)
}
Okay so lets start with binding your functions in the constructor, not in the markup, clean things up :P
Next, i'm not sure you understand how setting state works as your function goes against it's basic use. You are correctly setting the first state and using the callback (Because it takes time for state to actually be set) which is great. The callback function it's where it goes downhill.
Your mapping function is loading up several setState calls instantly, for each one console.log() will run successfully, but only one of the setStates will actually take effect. On top of that, even if it did work, your rows state will only have a single item. Lets try this:
onSearchHandler(e){
this.setState(prevState => {
return {
rows: prevState.rows.filter(searchingFor(e.target.value)),
searchTerm: e.target.value,
}
});
}
That will get you what I assume is the desired result... you should only ever do one setState at a time, unless you are waiting for the callback on each one, because you can't be sure each one will complete before the next.
Your logic is fine, but the code looks clumsy.I refactored the code so that only necessary logic is present and instead of bind use arrow functions.
Here, try this code on codeSandbox
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props){
super(props);
this.state = {
rows: ["asd", "bsd", "csd", "dsd", "esd"],
items: []
}
}
onSearchHandler = (e) => {
this.setState({ items: this.state.rows.filter(str => str.toLowerCase().includes(e.target.value.toLowerCase()))})
}
render(){
return (
<div>
<h3>Topics</h3>
<input type="text"
className="searchBar"
onChange={(e) => this.onSearchHandler(e)}/>
<p>{this.state.items.join('\n')}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

React setstate on big and small states

I have a state like this :
{
textfield: '',
data: [] //huge, used to render elements within the render()
}
When I want to update the textfield value (simple text input), I use this.setState({ textfield: newValue });. The problem is that there is some lag when I write a character in the field because it is re-rendering everything.
Is using shouldComponentUpdate() and deeply check my data object the only way to avoid re-rendering everything? Or is there a better/more efficient way?
Thanks
Am guessing its rerendering the entire component due to the state change on every key.
you could isolate your input element in a separate stateful component, hence only triggering a re-render on itself and not on your entire app.
So something like:
class App extends Component {
render() {
return (
<div>
...
<MyInput />
...
</div>
);
}
}
class MyInput extends Component {
constructor() {
super();
this.state = {textfield: ""};
}
update = (e) => {
this.setState({textfield: e.target.value});
}
render() {
return (
<input onChange={this.update} value={this.state.textfield} />
);
}
}

Resources