handlers inside a component invoking eachothers code by mistake - javascript

I'm lost! i have a really simple component but its going nuts.
i got an input with a change function and a button with onclick.
for some reason when i set the state in the onclick i get an 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.
Huh? im not doing any looping whats going on with this error then?
so i comment the line where i set the state and added a console log to see the value. now i discovered a true magic, when i run the code its showing in the console the value from my state without me clicking anything (the only place i use console.log is in the click event).
When i do click the button though, nothing really happens.
but listen to this, when i type inside my input, again the console logs the current value in the state!
why the console.log() call from the wrong method? is this a bug?
Here is my code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => {
console.log(value);
//this.setState({ myValue: value }); // this will error about infinite loop
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={this.handleChange} />
</div>
<button onClick={this.handleClick(this.state.myValue)}>set</button>
<h3>{this.state.myValue}</h3>
</div>
);
}
}
render(<App />, document.getElementById("root"));

The problem is that you are passing to the onClick event a function invocation, and not a function reference.
im not doing any looping whats going on with this error then?
In the first initial render call when you pass the handler function, you actually invoke it. This function is updating the state which triggers another render call which you pass again a function invocation that will do another update to the state which will trigger another render call and so on.
Hence an infinite loop.
why the console.log() call from the wrong method? is this a bug?
As i mentioned above, you are passing a function invocation, hence on each render you call console.log(value) instead of listening to the onClick event, and when you change the input (which works as expected) you rerender again and call console.log(value) once more. So it's not the handleChange that calling the console.log, it is render function that is calling handleClick which invoke console.log.
No bugs or magics here, it may not be the desired behavior but it is the expected behavior according to your code and logic.
You got 2 main options here.
Easy and fast fix: Pass a function reference that returns a function with your logic there. I'm using currying, so with ES6
arrow functions is just as easy as adding another parameter and an
arrow:
handleClick = (value) => (e) => {
console.log(value);
this.setState({ myValue: value });
};
This approach does have it's advantages, like fast implementation
and easy to understand and read the code but it may cause
performance issues.
You see, you are returning a new instance of a
function on each render and react will treat it as a new prop this
can interrupt the diffing algorithm of react.
A working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => (e) => {
console.log(value);
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={this.handleChange} />
</div>
<button onClick={this.handleClick(this.state.myValue)}>set</button>
<h3>{this.state.myValue}</h3>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
A better approach that is considered as best practice, is to compose a
component that accepts an event and a parameter as separate props
and will send this value upwards when the event is triggered.
For example MyButton.js:
class MyButton extends React.Component{
handleClick = e =>{
const {onClick, clickValue} = this.props;
this.props.onClick(clickValue);
}
render(){
const {children} = this.props;
return <button onClick={this.handleClick}>{children}</button>
}
}
A working example:
class MyButton extends React.Component{
handleClick = e =>{
const {onClick, clickValue} = this.props;
this.props.onClick(clickValue);
}
render(){
const {children} = this.props;
return <button onClick={this.handleClick}>{children}</button>
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = (value) => {
console.log(value);
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
const {myValue} = this.state;
return (
<div>
<div>
<input value={myValue} onChange={this.handleChange} />
</div>
<MyButton clickValue={myValue} onClick={this.handleClick}>set</MyButton>
<h3>{myValue}</h3>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
By the way, you don't need to bind the handlers to the class when you're using arrow functions. it binds the this automatically.
The body of ES6 arrow functions share the same lexical this as the
code that surrounds them, which gets us the desired result because of
the way that ES7 property initializers are scoped

As mentioned you are passing a function invocation than a function reference, so the simple and recommended change that one needs to do is to give a reference to a thick arrow function (or anonymous function):
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
myValue: "Hello World"
};
}
handleClick = (value) => {
console.log(value);
// No longer gives error
this.setState({ myValue: value });
};
handleChange = (e) => {
this.setState({ myValue: e.target.value });
}
render() {
return (
<div>
<div>
<input value={this.state.myValue} onChange={ () => {this.handleChange();} } />
</div>
<button onClick={ () => {this.handleClick(this.state.myValue);} }>set</button>
<h3>{this.state.myValue}</h3>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Now when the <App /> component is rendered it no longer has the function invocation as the value in onClick or onChange event listeners, instead, it has a reference to the anonymous function which in turn calls your handleClick() and handleChange() functions. Hope it helps! :)

Related

How to send data from Child to Parent with react? [duplicate]

passing data from child to parent component via callback function
but somehow it's not working.
what am I doing wrong here?
passing data from child to parent component - react - via callback function
https://codepen.io/silentarrowz/pen/GEMQEP?editors=0010
and here's the code
class App extends React.Component{
constructor(props){
super(props);
this.state={
input:'this is the input for now'
}
//this.handleInput=this.handleInput.bind(this);
}
handleInput(x){
this.setState({
input:x
});
alert(this.state.input);
}
render(){
return(
<div>
<h1>Passing props from Child to Parent Component</h1>
<Child getInput={this.handleInput} />
here's the input: {this.state.input}
</div>
);
}
}
class Child extends React.Component{
constructor(){
super();
this.state={
text:''
}
}
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
render(){
return(
<div>
<input type="text" placeholder="please input a name..." onChange={this.passingProps} />
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('app')
);
There are a couple of issues.
1) You have to bind passingProps
constructor(){
super();
this.state={
text:''
}
this.passingProps = this.passingProps.bind(this);
}
2) this.setState is asynchronous, so it's not guaranteed that this.state.text will be set to the value you want by the time you pass it to this.props.getInput. You can either do
this.props.getInput(newInput)
or
this.setState({ text: newInput }, () => {
this.props.getInput(this.state.text);
})
to resolve that issue.
class App extends React.Component{
constructor(props){
super(props);
this.state={
input:'this is the input for now'
}
this.handleInput=this.handleInput.bind(this);
}
handleInput(event){
let value = event.target.value;
this.setState({
input:value
});
}
render(){
return(
<div>
<h1>{this.state.input}</h1>
<Child getInput={this.handleInput} />
</div>
);
}
}
class Child extends React.Component{
constructor(){
super(props);
}
render(){
return(
<div>
<input type="text" placeholder="please input a name..." onChange={this.props.getInput} />
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('app')
);
Here is the answer for your question. I hope your proplem is solved.
In your Child Component, you have written following code:
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
The issue is due to the asynchronous behaviour of setState function. It means you can not call setState on one line and expect its updates on next line.
Use the callback function of setState to call the function of parent component just like this:
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({ text: newInput }, () => {
this.props.getInput(this.state.text);
})
}
Same thing is happening in handleInput function of App component.
this is not automatically bound in your passingProps function. Try arrow function syntax to bind it.
passingProps = e => {
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
Two things that you need to correct it:
if you want to access new state, you don't use this.state.input after
this.setState({input: 'xxx'}). Here is reason why not it.
this.passingProps = this.passingProps.bind(this) is defined what this is current scope. when you use this in component's function, this need to be bind.
Changed codepen
You can create a method in parent that accepts some data and then sets the received data as parent state.
Then pass this method to child as props. Now let the method accept child state as input and then let the method set the received child state as parent state.

react props undefined inside the constructor and exist inside the render

I'm trying to create a controlled text area.
class TextArea extends React.Component {
constructor(props) {
super(props);
this.state= {
text: this.props.initial
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
//some handle
}
render() {
return (
<textarea
value={this.state.text}
placeholder={this.props.initial}
onChange={this.handleChange}
/>
);
}
}
For some reason if I console.log the this.props.initial in the constructor I get an undefined.
But the placeholder works.
What I would like to do is to ditch the placeholder and set an initial value to that the user can edit and copy and interact with. (basically normal text and not a placeholder, but I cannot do that because it doesn't work)
What am I doing wrong?
Edit:
The way that I am passing props.initial to the textarea:
<TextArea
initial={this.state.json.initial}
text={this.state.json.text}
changeHandler={this.handleChange}
/>
I am getting the json from a $.getJSON call and I think that the textarea gets rendered before the json call is finished. Is there any way to run the render function only after the componentWillMount function?
Remove this from this.props in the constructor since you have access to props from its argument list.
class TextArea extends React.Component {
constructor(props){
super(props)
this.state = {
text: props.initial,
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event){
this.setState({ text: event.target.value })
}
render(){
return (
<div>
<div>Initial text: {this.props.initial}</div>
<textarea
value={this.state.text}
placeholder={this.props.initial}
onChange={this.handleChange}
/>
</div>
)
}
}
ReactDOM.render(
<TextArea />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You have to garantee that this.props.inital exists:
{ this.state.json && this.state.json.initial &&
<TextArea initial={this.state.json.initial} text={this.state.json.text} changeHandler={this.handleChange}/>
}
the important part to understand is that the constructor already takes in the props as parameters so you can access props directly in the constructor as props. no need to access it via this.props as long as you are inside the constructor
constructor(props) {
super(props);
this.state= {
text: props.initial
}
}
the code above should work
However, this is a better way of structuring a component like TextArea and it should also solve your problem of props.initial not having a value on runtime
First, you need to prepare the handleChange method in the parent component
class ParentComponent extends Component {
constructor(props) {
super(props)
this.state = {
myTextArea: ''
}
this.handleChange = this.handleChange.bind(this)
}
handleChange (e) {
this.setState({myTextArea: e.target.value})
}
render () {
return (
<TextArea
value={myTextArea}
onChange={this.handleChange}
/>
)
}
}
and on the text area component, you refer to onchange method passed through the props when defining the onchange method of your textarea.
<textarea
value={this.props.value}
placeholder="Something"
onChange={this.props.handleChange}
/>
the benefit of this approach is that one, the one that calls the textarea will always have an updated value, and two, this child element doesnt need to have a state. it makes managing a large react app easier and its the correct mind set to have once you start trying to implement Redux or similar frameworks to handle your state for you

passing data from child to parent component - react - via callback function

passing data from child to parent component via callback function
but somehow it's not working.
what am I doing wrong here?
passing data from child to parent component - react - via callback function
https://codepen.io/silentarrowz/pen/GEMQEP?editors=0010
and here's the code
class App extends React.Component{
constructor(props){
super(props);
this.state={
input:'this is the input for now'
}
//this.handleInput=this.handleInput.bind(this);
}
handleInput(x){
this.setState({
input:x
});
alert(this.state.input);
}
render(){
return(
<div>
<h1>Passing props from Child to Parent Component</h1>
<Child getInput={this.handleInput} />
here's the input: {this.state.input}
</div>
);
}
}
class Child extends React.Component{
constructor(){
super();
this.state={
text:''
}
}
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
render(){
return(
<div>
<input type="text" placeholder="please input a name..." onChange={this.passingProps} />
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('app')
);
There are a couple of issues.
1) You have to bind passingProps
constructor(){
super();
this.state={
text:''
}
this.passingProps = this.passingProps.bind(this);
}
2) this.setState is asynchronous, so it's not guaranteed that this.state.text will be set to the value you want by the time you pass it to this.props.getInput. You can either do
this.props.getInput(newInput)
or
this.setState({ text: newInput }, () => {
this.props.getInput(this.state.text);
})
to resolve that issue.
class App extends React.Component{
constructor(props){
super(props);
this.state={
input:'this is the input for now'
}
this.handleInput=this.handleInput.bind(this);
}
handleInput(event){
let value = event.target.value;
this.setState({
input:value
});
}
render(){
return(
<div>
<h1>{this.state.input}</h1>
<Child getInput={this.handleInput} />
</div>
);
}
}
class Child extends React.Component{
constructor(){
super(props);
}
render(){
return(
<div>
<input type="text" placeholder="please input a name..." onChange={this.props.getInput} />
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('app')
);
Here is the answer for your question. I hope your proplem is solved.
In your Child Component, you have written following code:
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
The issue is due to the asynchronous behaviour of setState function. It means you can not call setState on one line and expect its updates on next line.
Use the callback function of setState to call the function of parent component just like this:
passingProps(e){
var newInput=e.target.value;
//alert(newInput);
this.setState({ text: newInput }, () => {
this.props.getInput(this.state.text);
})
}
Same thing is happening in handleInput function of App component.
this is not automatically bound in your passingProps function. Try arrow function syntax to bind it.
passingProps = e => {
var newInput=e.target.value;
//alert(newInput);
this.setState({
text:newInput
});
this.props.getInput(this.state.text);
}
Two things that you need to correct it:
if you want to access new state, you don't use this.state.input after
this.setState({input: 'xxx'}). Here is reason why not it.
this.passingProps = this.passingProps.bind(this) is defined what this is current scope. when you use this in component's function, this need to be bind.
Changed codepen
You can create a method in parent that accepts some data and then sets the received data as parent state.
Then pass this method to child as props. Now let the method accept child state as input and then let the method set the received child state as parent state.

React state not updating in app but I can console.log correct updated state

I have a form I have created using es6 class. The form is stateful and updates its state onChange. The information in the state of the form is passed up to the app component onSubmit. I can console.log every step through through the state being passed up in the methods of my form and app component and it is behaving as expected. In this code example I have the console after I setState in app and it logs out the state object with the input value added as I would expect.
The problem is when I look in the react developer tools, the state has not updated. Also, if I move the console statement into a callback function in the setState method, it does not log anything.
My questions are how to fix this and more importantly, why would I be able to log out the state with the values I am looking for when the state in the application has does not appear to have actually updated?
class App extends Component {
constructor (props) {
super(props)
this.state = {
appointments: [{title:'first appointment'}]
};
this.updateAppointments = this.updateAppointments.bind(this);
}
updateAppointments(newAppointment) {
var newAppointmentList = this.state.appointments;
newAppointmentList.push(newAppointment);
this.setState= {
appointments: newAppointmentList,
//This console logs nothing
function() {
console.log(this.state.appointments);
}
};
//This console logs out the state as expected with the new appointment
//added even thought the state in the app does not appear to have the
//appointment added when I look in the react dev tools
console.log(this.state.appointments);
}
render() {
return (
<div className="App">
<AppointmentForm addAppointment = {this.updateAppointments} />
</div>
);
}
}
class AppointmentForm extends Component {
constructor (props) {
super(props)
this.state = {
appointmentTitle: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleTitleChange = this.handleTitleChange.bind(this);
}
handleTitleChange(event) {
this.setState({appointmentTitle: event.target.value});
}
handleSubmit(e) {
let newAppointment = {
title: this.state.appointmentTitle
}
e.preventDefault();
this.props.addAppointment(newAppointment);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<FormGroup controlId="appointmentTitle">
<ControlLabel>Appointment Title</ControlLabel>
<FormControl type="text" placeholder="Appointment Title" value={this.state.appointmentTitle}
onChange={this.handleTitleChange}/>
</FormGroup>
</form>
</div>
);
}
}
You are updating the state in wrong way.
Instead of:
this.setState = {
Write it like this:
updateAppointments(newAppointment) {
var newAppointmentList = this.state.appointments.slice();
newAppointmentList.push(newAppointment);
this.setState({
appointments: newAppointmentList, () => {
console.log(this.state.appointments);
}
})
}
Suggestion: Never mutate the state value directly, so first create a copy of state array by using slice(), push the new value then use setState to update the state.
You have a code error. You're setting the setState property, not calling the setState function. Change this:
this.setState= {
appointments: newAppointmentList,
function() {
console.log(this.state.appointments);
}
};
to this:
this.setState({
appointments: newAppointmentList,
function() {
console.log(this.state.appointments);
}
});

debounce textarea input with react/redux

I'm trying to debounce textarea value with react/redux and show the debounced value in div#preview but i'm getting synthetic event warning after first function call.
I have reducer for handling textarea value state which is working as intended, but for simplicity i've wrote local state in this snippet.
If there is a better method besides debounce to avoid react rerendering after each keypress I would love to know. Thanks in advance.
class TextArea extends React.Component {
constructor(props){
super(props);
this.state = {foo: ''}
}
handleInputChange = _.debounce((e) => {
e.persist();
let value = e.target.value;
this.setState({foo: value});
}, 300);
render() {
return (
<div>
<textarea onChange={(e)=>this.handleInputChange(e)} value={this.state.foo}></textarea>
<p id="preview">{this.state.foo}</p>
</div>
);
}
}
ReactDOM.render(
<TextArea />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You get this error because you try to .persist() the event inside the debounce's timeout. When the timeout invokes the callback, the synthetic event was already released. So you'll have to persist the event outside of the debounce.
However, your idea has another problem. Since the textbox is a controlled component, debouncing the updated value, would cause the textbox to render (part of) the text only after the used stopped typing.
To prevent that you need to update the state for the controlled element immediately, and debounce the update for the display state (or the redux action dispatch).
For example:
class TextArea extends React.Component {
constructor(props){
super(props);
this.state = { foo: '', controlled: '' }
}
updateFoo = _.debounce((value) => { // this can also dispatch a redux action
this.setState({foo: value});
}, 300);
handleInputChange = (e) => {
const value = e.target.value;
this.setState({
controlled: value
});
this.updateFoo(value);
}
render() {
return (
<div>
<textarea onChange={ this.handleInputChange }
value={this.state.controlled} />
<p id="preview">{this.state.foo}</p>
</div>
);
}
}
ReactDOM.render(
<TextArea />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>
The other answer already covered the problem with persisting the event. For the input debouncing aspect, you may want to read my blog post Practical Redux, Part 7: Form Change Handling. In that post, I show a reusable component that can handle debouncing text input updates for the rest of the application, while allowing them to re-render immediately with the current value.
I struggled a lot with this and wasn't able to achieve a satisfying result by myself. Eventually, I used https://github.com/nkbt/react-debounce-input which works perfectly and is much simpler than my previous failed attempts.
/**
* Updates the current node "text" value.
*
* #param event
*/
const onTextInputValueChange = (event: any) => {
const newValue = event.target.value;
patchCurrentNode({
data: {
text: newValue,
},
} as InformationNodeData);
};
<DebounceInput
element={'textarea'}
debounceTimeout={500}
placeholder={'Say something here'}
onChange={onTextInputValueChange}
value={node?.data?.text}
/>
patchCurrentNode writes to my Recoil store.
The DebounceInput component handles an internal state to display the latest value, while only updating the value in the store only once in a while.
Implementation isn't specific to Recoil, and would likely work like a charm using Redux.
Functional component:
import React, { useState, useMemo } from "react";
import debounce from "lodash.debounce";
export default function TextArea() {
const [foo, setFoo] = useState("");
const [controlled, setController] = useState("");
const updateFoo = useMemo(
() =>
debounce((value) => {
// this can also dispatch a redux action
setFoo(value);
}, 1000),
[]
);
const handleInputChange = (e) => {
const value = e.target.value;
setController(value);
updateFoo(value);
};
return (
<div>
<textarea onChange={handleInputChange} value={controlled} />
<p id="preview">{foo}</p>
</div>
);
}

Categories