React this.setState With Arrow Function Causes Error In Console - javascript

I have a very simple form where I'm storing a users email in the component's state and updating the state with an onChange function. There is a strange thing that is occurring where if my onChange function updates the state with a function I get two errors in the console whenever I'm typing. If I update the state with an object, however, I get no errors. I believe updating with a function is the recommended method so I'm curious to know why I'm getting these errors.
My Component:
import * as React from 'react';
import { FormGroup, Input, Label } from 'reactstrap';
interface IState {
email: string;
}
class SignUpForm extends React.Component<{}, IState> {
constructor(props: {}) {
super(props);
this.state = {
email: ''
};
}
public onEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(() => ({ email: event.currentTarget.value }))
};
// Using this function instead of the one above causes no errors
// public onEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// this.setState({ email: event.currentTarget.value })
// };
public render() {
return (
<div>
<h1>Sign Up</h1>
<div className='row' style={{ paddingTop: '20px' }}>
<div className='col-lg-4 offset-lg-4'>
<form>
<FormGroup style={{ textAlign: 'left' }}>
<Label>Email</Label>
<Input
value={this.state.email}
onChange={this.onEmailChange}
type='text'
placeholder='Email Address'
/>
</FormGroup>
</form>
</div>
</div>
</div>
);
}
}
export default SignUpForm;
The error messages I get are:
index.js:2178 Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method `currentTarget` on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist(). See react-event-pooling for more information.
index.js:2178 Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: react-controlled-components

If your state update is derived from what is currently in your state (e.g. incrementing a count variable) you should use the update function version of setState.
If you are just setting a completely new value like you do with an event handler, you don't need to use the update function version. The commented out version in your question is perfectly fine.
If you want to use the update function version you must either use event.persist() so that you can use the event asynchronously, or simply extract the value before you call setState.
public onEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
this.setState(() => ({ email: value }))
};

You can't use event or any of its descendant properties once the event handler has terminated. Instead, you have to grab the value, then use the function (or if you prefer, use persist, as Tholle points out):
public onEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
this.setState(() => ({ email: value }));
};
That said, since you're not updating state based on state or props, this is one of the few situations where using the non-callback version of setState is fine (details):
public onEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ email: event.currentTarget.value });
};

Related

How to properly use React.creatRef()

I've been beating my head against the wall and cross referencing code I've read from others and questions that have been asked. I'm at the point where I know I'm just not looking in the right direction, however I'm not sure where to turn.
The app I'm writing was originally written by me in 2019, and I did not know of React h
Hooks as I learned components from a coding bootcamp. TL;DR: I'm rusty.
The issue:
I'm trying to use AXIOS to use a PUT call into my MongoDB, and the way I learned was by using refs. Refs in the way I learned is now deprecated, and I just want to get this working so I can move on to another project and start using Hooks instead.
When I use my button created to save the change, I get an error in the browser console, however it refreshes too fast for me to catch the exact error. The second I get to look at the wall of text, it looks similar to a bad promise, but I'm not entirely sure. Either way, the data does not get updated.
My code:
import React, { Component } from 'react'
import axios from 'axios'
export default class EditSeed extends Component {
constructor(props){
super(props)
this.state = {
_id: '',
seed: '',
created_at: '',
__v: ''
}
this.changeHandler = this.changeHandler.bind(this)
this.myRef = React.createRef
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
this.textInput.current.focus();
}
componentDidMount(){
this.fetchSeed()
}
fetchSeed = async () => {
try {
const res = await axios.get(`/api/grapevine/${this.props.match.params.id}`)
this.setState({
_id: res.data._id ,
seed: res.data.seed,
created_at: res.data.created_at,
__v: res.data.__v
})
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
editSeed = async (newSeed) => {
try {
const res = await axios.request({
method: 'put',
url: `/api/grapevine/${this.state._id}`,
data: newSeed
})
res.this.props.history.push(`/`)
}
catch (err) {
this.setState({ error: err.message })
console.log(err)
}
}
onSubmit = (e) => {
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
e.preventDefault()
}
changeHandler = (e) => {
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
render(){
return(
<div>
<h1>Edit Seed</h1>
<form onSubmit={this.onSubmit.bind(this)}>
<label>
Edit Message:
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
</label>
<input type="submit" value="Save" />
</form>
</div>
)
}
}
My fetch function works as intended, it's mainly just the edit that doesn't want to work. I have tried changing
<input type="text" name="seed" ref={this.myRef} value={this.state.seed} onChange={this.changeHandler} />
to
<input type="text" name="seed" ref={this.myRef.current} value={this.state.seed} onChange={this.changeHandler} />
Obviously I am doing something wrong, and I have referenced and read the React docs on this about 10 times and I'm really just hitting a wall.
Any insight would be massively appreciated.
Firstly, React.createRef is a function, so it needs to be invoked.
this.myRef = React.createRef();
Then you simply attach the ref to the element.
<input
ref={this.myRef}
type="text"
name="seed"
value={this.state.seed}
onChange={this.changeHandler}
/>
Then whenever you need to reference the input's ref you access the current value of it.
this.myRef.current
Update
I don't think the React ref is providing much for you here since you duplicate the seed and created_at into local state. You update the seed state via the onChange handler. Why not just forego the ref and access the state in the submit handler. I believe this will resolve your "cannot access value of undefined" error since the input element target object likely doesn't have seed or created_at attributes to access a value of.
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.state.seed,
created_at: this.state.created_at // is this supposed to be a current datetime?
}
this.editSeed(newSeed);
}
changeHandler = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
You did nearly got it right, just a few minor changes will lead you to the right way:
First: You need to invoke React.createRef function:
this.myRef = React.createRef -> this.myRef = React.createRef()
Second: You need to call e.preventDefault() from the begining of your handler so that the whole page will not get refeshed imediately. Something like this:
onSubmit = (e) => {
e.preventDefault();
const newSeed = {
seed: this.myRef.current.seed.value,
created_at: this.myRef.current.created_at.value
}
this.editSeed(newSeed)
}
From then, I think you will easily get to the right path.
You can use useRef hook in functional components like this:
import React, {useRef} from 'react';
function MyComponenet () {
myRef = useRef(null) // <---- initialize your ref with null
return (
<div ref=myRef >
Hello World
</div>
)
}
now you create a reference and initiate it with a null value, then passed it to the div element. from now you can manipulate your ref like get its value or assign values, click, and other actions.
with TypeScript
myRef = useRef<HTMLDivElement>(null)
if you are passing myRef to a div element, so your ref type will be HTMLDivElement

Best approach for using same component for editing and adding data. Mixing component state with redux store

I'm building web app in React with Redux. It is simple device manager. I'm using the same component for adding and updating device in database. I'm not sure, if my approach is correct. Here you can find parts of my solution:
UPDATE MODE:
In componentDidMount I'm checking, if deviceId was passed in url (edit mode). If so, I'm calling redux action to get retrieve data from database. I'm using connect function, so when response arrives, It will be mapped to component props.
Here is my mapStateToProps (probably I should map only specific property but it does not matter in this case)
const mapStateToProps = state => ({
...state
})
and componentDidMount:
componentDidMount() {
const deviceId = this.props.match.params.deviceId;
if (deviceId) {
this.props.getDevice(deviceId);
this.setState({ editMode: true });
}
}
Next, componentWillReceiveProps will be fired and I will be able to call setState in order to populate inputs in my form.
componentWillReceiveProps(nextProps) {
if (nextProps.devices.item) {
this.setState({
id: nextProps.devices.item.id,
name: nextProps.devices.item.name,
description: nextProps.devices.item.description
});
}
}
ADD MODE:
Add mode is even simpler - I'm just calling setState on each input change.
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
That's how my inputs looks:
<TextField
onChange={this.handleChange('description')}
label="Description"
className={classes.textField}
value={this.state.description}
/>
I don't like this approach because I have to call setState() after receiving data from backend. I'm also using componentWillReceiveProps which is bad practice.
Are there any better approaches? I can use for example only redux store instead of component state (but I don't need inputs data in redux store). Maybe I can use React ref field and get rid of component state?
Additional question - should I really call setState on each input onChange?
To avoid using componentWillReceiveProps, and because you are using redux, you can do:
class YourComponent extends React.Component {
state = {
// ...
description: undefined,
};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.description === undefined && nextProps.description) {
return { description: nextProps.description };
}
return null;
}
componentDidMount() {
const deviceId = this.props.match.params.deviceId;
if (deviceId) {
this.props.getDevice(deviceId);
this.setState({ editMode: true });
}
}
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
// ...
render() {
let { description } = this.state;
description = description || ''; // use this value in your `TextField`.
// ...
return (
<TextField
onChange={this.handleChange('description')}
label="Description"
className={classes.textField}
value={description}
/>
);
}
}
const mapStateToProps = (state) => {
let props = { ...state };
const { devices } = state;
if (devices && devices.item) {
props = {
...props,
id: devices.item.id,
name: devices.item.name,
description: devices.item.description,
};
}
return props;
};
export default connect(
mapStateToProps,
)(YourComponent);
You can then access id, name, and description thought this.props instead of this.state. It works because mapStateToProps will be evaluated every time you update the redux store. Also, you will be able to access description through this.state and leave your TextField as is. You can read more about getDerivedStateFromProps here.
As for your second question, calling setState every time the input changes is totally fine; that's what's called a controlled component, and the react team (nor me) encourage its use. See here.

Updating from an input element in ReactJS

I'm attempting to create a page using React, whereby I can update a single element of the state; here is how the state is defined:
interface MyState {
data?: MyData;
loading: boolean;
}
interface MyData {
id: number;
description: string;
test: string;
}
I have the following inside my render function:
return <div>
<h1>Description: {myData.description}</h1>
<br/><br/>
<input type="text" value={emailType!.test} onChange={this.handleChange} />
</div>;
And the handleChange (which is the heart of my issue):
handleChange(event: React.FormEvent<HTMLInputElement>) {
this.setState({ emailType!.test: event.currentTarget.value });
}
As I'm using tsx, the function above won't even compile. However, it does illustrate what I'm trying to do. Typically, when I've called this.setState, I've done so with a full state (that is, I know the entire new state). In this case, I only want to change the contents of a single field: is that possible?
setState allows changing only top-level key in the state like
handleChange = (event: React.FormEvent<HTMLInputElement>) => {
const emailType = { ...emailType, test: event.currentTarget.value }
this.setState({ emailType })
}
Don't forget to bind your function to the proper context.
Another option is to use function as a parameter in setState:
this.setState((oldState) => {
return {
...oldState,
emailType: {
...oldState.emailType,
test: event.currentTarget.value
}
}
})

React: trigger onChange if input value is changing by state?

Edit: I don't want to call handleChange only if the button has been clicked. It has nothing to do with handleClick. I gave an example in the #shubhakhatri answer's comment.
I want to change the input value according to state, the value is changing but it doesn't trigger handleChange() method. How can I trigger handleChange() method ?
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 'random text'
}
}
handleChange (e) {
console.log('handle change called')
}
handleClick () {
this.setState({value: 'another random text'})
}
render () {
return (
<div>
<input value={this.state.value} onChange={this.handleChange}/>
<button onClick={this.handleClick.bind(this)}>Change Input</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
Here is the codepen link: http://codepen.io/madhurgarg71/pen/qrbLjp
You need to trigger the onChange event manually. On text inputs onChange listens for input events.
So in you handleClick function you need to trigger event like
handleClick () {
this.setState({value: 'another random text'})
var event = new Event('input', { bubbles: true });
this.myinput.dispatchEvent(event);
}
Complete code
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 'random text'
}
}
handleChange (e) {
console.log('handle change called')
}
handleClick () {
this.setState({value: 'another random text'})
var event = new Event('input', { bubbles: true });
this.myinput.dispatchEvent(event);
}
render () {
return (
<div>
<input readOnly value={this.state.value} onChange={(e) => {this.handleChange(e)}} ref={(input)=> this.myinput = input}/>
<button onClick={this.handleClick.bind(this)}>Change Input</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
Codepen
Edit:
As Suggested by #Samuel in the comments, a simpler way would be to call handleChange from handleClick if you don't need to the event object in handleChange like
handleClick () {
this.setState({value: 'another random text'})
this.handleChange();
}
I hope this is what you need and it helps you.
I tried the other solutions and nothing worked. This is because of input logic in React.js has been changed. For detail, you can see this link: https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04.
In short, when we change the value of input by changing state and then dispatch a change event then React will register both the setState and the event and consider it a duplicate event and swallow it.
The solution is to call native value setter on input (See setNativeValue function in following code)
Example Code
import React, { Component } from 'react'
export class CustomInput extends Component {
inputElement = null;
// THIS FUNCTION CALLS NATIVE VALUE SETTER
setNativeValue(element, value) {
const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
const prototype = Object.getPrototypeOf(element);
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
if (valueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value);
} else {
valueSetter.call(element, value);
}
}
constructor(props) {
super(props);
this.state = {
inputValue: this.props.value,
};
}
addToInput = (valueToAdd) => {
this.setNativeValue(this.inputElement, +this.state.inputValue + +valueToAdd);
this.inputElement.dispatchEvent(new Event('input', { bubbles: true }));
};
handleChange = e => {
console.log(e);
this.setState({ inputValue: e.target.value });
this.props.onChange(e);
};
render() {
return (
<div>
<button type="button" onClick={() => this.addToInput(-1)}>-</button>
<input
readOnly
ref={input => { this.inputElement = input }}
name={this.props.name}
value={this.state.inputValue}
onChange={this.handleChange}></input>
<button type="button" onClick={() => this.addToInput(+1)}>+</button>
</div>
)
}
}
export default CustomInput
Result
I think you should change that like so:
<input value={this.state.value} onChange={(e) => {this.handleChange(e)}}/>
That is in principle the same as onClick={this.handleClick.bind(this)} as you did on the button.
So if you want to call handleChange() when the button is clicked, than:
<button onClick={this.handleChange.bind(this)}>Change Input</button>
or
handleClick () {
this.setState({value: 'another random text'});
this.handleChange();
}
In a functional component you can do this, let's assume we have a input[type=number]
const MyInputComponent = () => {
const [numberValue, setNumberValue] = useState(0);
const numberInput = useRef(null);
/**
* Dispatch Event on Real DOM on Change
*/
useEffect(() => {
numberInput.current.dispatchEvent(
new Event("change", {
detail: {
newValue: numberValue,
},
bubbles: true,
cancelable: true,
})
);
}, [numberValue]);
return (
<>
<input
type="number"
value={numberValue}
ref={numberInput}
inputMode="numeric"
onChange={(e) => setNumberValue(e.target.value)}
/>
</>
)
}
The other answers talked about direct binding in render hence I want to add few points regarding that.
You are not recommended to bind the function directly in render or anywhere else in the component except in constructor. Because for every function binding a new function/object will be created in webpack bundle js file hence the bundle size will grow. Your component will re-render for many reasons like when you do setState, new props received, when you do this.forceUpdate() etc. So if you directly bind your function in render it will always create a new function. Instead do function binding always in constructor and call the reference wherever required. In this way it creates new function only once because constructor gets called only once per component.
How you should do is something like below
constructor(props){
super(props);
this.state = {
value: 'random text'
}
this.handleChange = this.handleChange.bind(this);
}
handleChange (e) {
console.log('handle change called');
this.setState({value: e.target.value});
}
<input value={this.state.value} onChange={this.handleChange}/>
You can also use arrow functions but arrow functions also does create new function every time the component re-renders in certain cases. You should know about when to use arrow function and when are not suppose to. For detailed explation about when to use arrow functions check the accepted answer here
you must do 4 following step :
create event
var event = new Event("change",{
detail: {
oldValue:yourValueVariable,
newValue:!yourValueVariable
},
bubbles: true,
cancelable: true
});
event.simulated = true;
let tracker = this.yourComponentDomRef._valueTracker;
if (tracker) {
tracker.setValue(!yourValueVariable);
}
bind value to component dom
this.yourComponentDomRef.value = !yourValueVariable;
bind element onchange to react onChange function
this.yourComponentDomRef.onchange = (e)=>this.props.onChange(e);
dispatch event
this.yourComponentDomRef.dispatchEvent(event);
in above code yourComponentDomRef refer to master dom of your React component for example <div className="component-root-dom" ref={(dom)=>{this.yourComponentDomRef= dom}}>
Approach with React Native and Hooks:
You can wrap the TextInput into a new one that watches if the value changed and trigger the onChange function if it does.
import React, { useState, useEffect } from 'react';
import { View, TextInput as RNTextInput, Button } from 'react-native';
// New TextInput that triggers onChange when value changes.
// You can add more TextInput methods as props to it.
const TextInput = ({ onChange, value, placeholder }) => {
// When value changes, you can do whatever you want or just to trigger the onChange function
useEffect(() => {
onChange(value);
}, [value]);
return (
<RNTextInput
onChange={onChange}
value={value}
placeholder={placeholder}
/>
);
};
const Main = () => {
const [myValue, setMyValue] = useState('');
const handleChange = (value) => {
setMyValue(value);
console.log("Handling value");
};
const randomLetters = [...Array(15)].map(() => Math.random().toString(36)[2]).join('');
return (
<View>
<TextInput
placeholder="Write something here"
onChange={handleChange}
value={myValue}
/>
<Button
title='Change value with state'
onPress={() => setMyValue(randomLetters)}
/>
</View>
);
};
export default Main;
I know what you mean, you want to trigger handleChange by click button.
But modify state value will not trigger onChange event, because onChange event is a form element event.
I had a similar need and end up using componentDidMount(), that one is called long after component class constructor (where you can initialize state from props - as an exmple using redux )
Inside componentDidMount you can then invoke your handleChange method for some UI animation or perform any kind of component properties updates required.
As an example I had an issue updating an input checkbox type programatically, that's why I end up using this code, as onChange handler was not firing at component load:
componentDidMount() {
// Update checked
const checkbox = document.querySelector('[type="checkbox"]');
if (checkbox)
checkbox.checked = this.state.isChecked;
}
State was first updated in component class constructor and then utilized to update some input component behavior
Try this code if state object has sub objects like this.state.class.fee. We can pass values using following code:
this.setState({ class: Object.assign({}, this.state.class, { [element]: value }) }

How to properly manage forms in React/Redux app

I am following some tutorials about React/Redux.
The part that is not clear to me is input forms.
I mean how to handle changes and where to manage state
This is a very simple form with just one field.
export default class UserAdd extends React.Component {
static propTypes = {
onUserSubmit: React.PropTypes.func.isRequired
}
constructor (props, context) {
super(props, context);
this.state = {
name: this.props.name
};
}
render () {
return (
<form
onSubmit={e => {
e.preventDefault()
this.handleSubmit()
}}
>
<input
placeholder="Name"
onChange={this.handleChange.bind(this)}
/>
<input type="submit" value="Add" />
</form>
);
}
handleChange (e) {
this.setState({ name: e.target.value });
}
handleSubmit () {
this.props.onUserSubmit(this.state.name);
this.setState({ name: '' });
}
}
I feel this like breaking Redux philosophy, because a presentation component is updating the state, am I right?
This is the connected component to be coupled with the presentation component.
const mapDispatchToProps = (dispatch) => {
return {
onUserSubmit: (name) => {
dispatch(addUser(name))
}
}
}
const UserAddContainer = connect(
undefined,
mapDispatchToProps
)(UserAdd)
Is this the correct way to follow, or am i mixing things up?
Is correct to call setState in UserAdd component and updating state on every key pressed (handleChange) ?
Thank you
There is a nice library Redux Form for handling forms by updating global store in a Redux way. With it's help you shouldn't have to set up actions for each input, just the whole form and its state. Check it out.
The main principle of this library, consists in updating inputs value by dispatching redux actions, not using setState stuff. For every form in the app, there is a separate property in the global state. Every blur, onChange, submit events dispatches an action that mutates the state. Action creators are common for all the forms, no need to declare them for every form apart, just pass form id or name in payload to the reducer, so it could know which form`s property should be updated.
For example. There should be set a property form as a plain object in the app state. Each new form in the application, should store it's state in it. Let's give your form a name attribute, so it should serve us as the identificator.
render () {
return (
<form
name="AForm"
onSubmit={e => {
e.preventDefault()
this.handleSubmit()
}}
>
<input
name="name"
placeholder="Name"
onChange={this.handleChange.bind(this)}
/>
<input type="submit" value="Add" />
</form>
);
}
Since it has just one property Name, form state should now have a structure like:
form: {
AForm: {
Name: {
value: '',
error: ''
}
}
}
Also, there should be an action creator:
export function onFormFieldChange(field) {
return {
type: "redux-form/CHANGE"
field: field.name
value: field.value,
form: field.form
}
}
All needed data should be passed as the pay load so, the reducer will know now what form and what field to update.
Now, when the form component is being connected, this action creator should be set as a property:
import { onFormFieldChange } from `path-to-file-wit-actions`
const mapStateToProps = (state, ownProps) => {
const { AForm } = state.form
return {
name: AForm.name
}
}
const mapDispatchToProps = (dispatch) => {
return {
onChange: (e) => {
dispatch(onFormFieldChange({
field: 'name',
value: e.target.value,
form: 'AForm'
}))
},
onUserSubmit: (name) => {
dispatch(addUser(name))
}
}
}
const UserAddContainer = connect(
undefined,
mapDispatchToProps
)(UserAdd)
In the component, field value and onChange event handler should now be taken from props:
<input placeholder="Name" name="this.props.name.value" onChange={this.props.handleChange.bind(this)} />
So, form is being handled in a "Redux" way. On every key press, global state will be updated and input will be rerendered with it's new value. Similar thing should be done with other events, like onBLur, onFocus, onSubmit etc. Since it's a lot work to do, it's much more comfrotable to use Redux Form.
It's a very rough example. Nearly each line of code could be enhanced, hope you'll understand what was meant.
I usually store my form state inside a form component using this.setState() and only fire an action with the complete form object, which gets passed to some sort of POST ajax call.

Categories

Resources