React Component State Being Reset to Default - javascript

I am trying to track a form in this.state with event handlers on the inputs, but for some reason this.state is being reset back to its default state when the event handler tries to update it. This is an example of what the Component looks like.
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: {}
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: reportForm })
}
render(){
return(
<form>
<input className="example" type="text" onChange={(e) => {this.textInputHandler(e)}} />
</form>
)
}
}
Before textInputHandler is called, this.state has an object and array stored in it, but once setState is called they are reset back to the default in the constructor. On subsequent updates to the text input this.state.form persists but everything else is reset. How can I prevent this from happening?
UPDATE
After trying some of the solutions suggested below I went back and logged this.state at just about every possible point and found that it is reset even before setState() is being called in the input handler.

You forget about input value and update state by ref (make mutation).
class ExampleReport extends React.Component {
constructor(props) {
super(props)
this.state = {
reportDetails: null,
report: this.props.report,
form: { example: '', },
}
this.textInputHandler = this.textInputHandler.bind(this)
}
textInputHandler(e) {
const { target: { name, value } } = e;
this.setState(prevState => ({
...prevState,
form: {
...prevState.form,
[name]: value,
},
}));
}
render() {
const { form: { example } } = this.state;
return (
<form>
<input
className="example"
type="text"
name="example"
value={example}
onChange={this.textInputHandler}
/>
</form>
)
}
}

Once way to solve this would be like below, spreading reportForm in a new object
textInputHandler(e) {
var reportForm = this.state.form;
var target = e.target;
var name = target.className;
var value = target.value;
reportForm[name] = value;
this.setState({ form: { ...reportForm } }) // <----------------------
}
However you may want to use more declarative solution as provided by Kirill but without unnecessary state changes
textInputHandler(e) {
const { className: name, value } = e.target;
this.setState(prevState => { form: { ...prevState.form, [name]: value } }) // <----------------------
}

try changing your onChange to this
onChange={this.textInputHandler}
like how they have it in the react documentation
EX)
<input type="text" value={this.state.value} onChange={this.handleChange} />
then do the spreading stuff
don't add, e.preventDefault because that only works for submitting
Look at Krill answer he has everything you need on there
compare it to yours from the top, you have a lot of stuff missing

Related

React state updated before making a call to the function that updates it

I have a situation where I am passing params from Child Class to Parent on callback. Even before the Parent call executes the part where the state update has to happen, the state is already updated before that part is executed. For example, in the code below, the first console.log statement already has an updated value of state variable x set to the new one in the Parent class. This is very surprising.
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
...this.props,
};
this.handleCallback = this.handleCallback.bind(this);
}
handleCallback = (x) => {
console.log('1 CALLBACK in Parent', this.state);
this.setState({x});
console.log('2 CALLBACK in Parent', this.state);
}
render() {
return (
<Child onSubmit={this.handleCallback}>
)
}
}
export default class Child extends Component {
constructor() {
super();
this.state = {
x: {
y: 1,
z: 2
}
};
}
handleInputChange = event => {
const {name, value} = event.target;
this.setState((prevState) => ({
x: {
...prevState.x,
[name]: value
}
}));
};
handleSubmit = event => {
this.props.onSubmit(this.state.x);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
.
// update to {this.state.x} happens here in input form
<input className='inputText' type='text' name='y' placeholder={this.state.x.y} onChange={this.handleInputChange} />
<input className='inputText' type='text' name='z' placeholder={this.state.x.z} onChange={this.handleInputChange} />
.
)
}
}
I am wondering what could be going on wrong here and what is the best practice here ?

How to change the state in react with a form

I have a form that is supposed to update the initial state, I've followed many tutorials and my code looks the same as them but for some reason it doesn't update the state.
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState({title})
}
onBodyChange(e) {
const body = e.target.value;
this.setState({body})
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState(() => ({ error: "Please fill in all gaps"}))
} else {
this.setState(() => ({ error: "" }))
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
When I check the current state in the React developer tools it shows that the state remains the same and I don't know why because there are not errors in the log.
I'm working with webpack, babel and react.
////////////////////
EDITED
////////////////////
I edited my code following the suggestions you guys gave me but still it doesn't work. An alert is supposed to appear when submitted the form but that doesn't get fired either, so I believe that non of the functions are getting fired.
This is my edited code:
import React, { Component } from 'react';
class Create extends Component{
constructor(props){
super(props);
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
title: "",
body: "",
error: ""
}
}
onTitleChange(e) {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
}
onBodyChange(e) {
const body = e.target.value;
this.setState((prevState) => {
return {
...prevState,
body
};
});
}
onSubmit(e) {
e.preventDefault();
if(!this.state.title || !this.state.body){
this.setState((prevState) => {
return {
...prevState,
error: "Please fill in all gaps"
};
});
} else {
this.setState((prevState) => {
return {
...prevState,
error: ""
};
});
// Send info to the main page
alert(this.state.title);
}
}
render(){
return(
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit = {this.onSubmit}>
<label>Put a title for your note</label>
<input
placeholder="Title"
type="text"
value={this.state.title}
autoFocus
onChange= {this.onTitleChange}
/>
<label>Write your note</label>
<textarea
placeholder="Note"
value={this.state.body}
autoFocus
onChange = {this.onBodyChange}
/>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default Create;
You should try binding the event handlers in the constructor, because it seems like this within those event handling functions could be undefined. The React documentation outlines why the binding is necessary, and here's another useful page on handling forms in React.
constructor(props) {
super(props);
...
this.onTitleChange = this.onTitleChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onSubmitChange = this.onSubmitChange.bind(this);
}
An alternative to binding in the constructor would also be to use arrow functions for your event handlers which implicitly bind this.
class Create extends Component {
...
onTitleChange = () => { ... }
onBodyChange = () => { ... }
onSubmitChange = () => { ... }
}
EDIT: Can't comment on your post since my reputation is too low, but it seems like there's a typo in the changes you just made.
this.onSubmitC = this.onSubmit.bind(this) should be changed to
this.onSubmit = this.onSubmit.bind(this)
React's setState() accepts an object, not a function. So change your onSubmit() to this:
if(!this.state.title || !this.state.body){
this.setState({ error: "Please fill in all gaps"})
} else {
this.setState({ error: "" })
// Send info to the main page
alert(this.state.title);
}
It better to use the previous state and only update the required (input value) values.
in your case you are replacing the existing state (whole object) just with the title and the same goes for body onBodyChange
onTitleChange () {
const title = e.target.value;
this.setState((prevState) => {
return {
...prevState,
title
};
});
};
<

React-Redux: State always one input behind

I've created a class based component that renders an input field. I need the global state to update while the user types into the input. The issue is that the global state is always one step (render?) behind from what's actually in the input field. For example, when I write “winners” in the input, the state is “winner” instead. How can I fix this?
Component
class TeamCardT1 extends Component {
constructor(props) {
super(props);
// local state
this.state = {
team_one_name: "Team One",
};
// bind events
this.handleName = this.handleName.bind(this);
};
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
});
this.props.handleSubmit(this.state);
};
render() {
const { team_one_name } = this.state;
return (
<>
<div className="teamCard_container">
<input
type="text"
id="team_one_name"
name="team_one_name"
value={team_one_name}
onChange={this.handleName}
maxLength="35"
minLength="2"
className="teamCard_teamName" />
<PlayersCardT1 />
<ScoreCard />
</div>
</>
)
};
}
index.js for the component
const mapStateToProps = ({ team_one_name }) => {
return {
team_one_name,
};
};
// Dispatch
const mapDispatchToProps = dispatch => {
return {
handleSubmit: (data) => { dispatch(updateTeamOneName(data)) }
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TeamCardT1);
You handleSubmit with the previous state, change to the current value.
handleName = e => {
this.setState({ team_one_name: e.target.value });
this.props.handleSubmit(e.target.value);
};
Notice that you already have a shallow merge with setState so you don't need to destruct this.state.
state is one step behind because you should call the prop function as a setState callback by this way the prop function will call just after the state set.
handleName = e => {
this.setState({
...this.state,
team_one_name: e.target.value
}, () => {
this.props.handleSubmit({value: e.target.value});
});
};

Perform validation before every render

I'm working on this component and I want to validate its fields before every render. If they are not valid I want to disable a button in the parent.
Here is what I've got thus far:
export default function Input(props) {
const [inputs] = useState(props.inputs);
const didChangeRef = useRef([]);
useEffect(() => {
if (!_.isEqual(didMountRef.current, props.inputs)) {
validateInput(input);
didChangeRef.current = props.inputs;
}
});
const validateInput = input => {
const errors = useValidation(input);
if(Object.keys(errors).length !== 0) {
props.setIsValid(false);
}
}
return (
<input
onChange={e=> props.setProperty(e)}>
</input>
<input
onChange={e=> props.setProperty(e)}>
</input>
)
}
If an input is changed, it sets a property in the parent and this component is re-rendered. Inputs is an array of objects and I wish to validate it's contents on each render (or componentDidMount). I either manage to get it to loop infinitely or run the validation only once.
I appreciate your help.
P.S.:
I tried another approach as well, but it still ends up looping infinately:
export default function Input(props) {
const didMountRef = useRef(true);
useLayoutEffect(() => {
if (didMountRef.current) {
didMountRef.current = false;
return;
}
validate(input);
});
const validate = input => {
// validation...
}
}
Here is the parent component:
class CreateShoppingItem extends React.Component {
constructor(props) {
super(props);
this.state = {
storage: {},
isValid: true,
};
}
setIsValid = (isValid) => {
this.setState({ isValid });
};
render() {
return (
<div>
<Input setIsValid={this.setIsValid} inputs={this.storage.inputs} />
<Button disable={!isValid}></Button>
</div>
);
}
}

Modify data in children component and make it available in the parent as props

I am trying to modify some state parameters inside a child component and then pass it for use in the parent.
I have a state object called driver. Inside of my EditDriver component, I am calling ProfileTab and passing it the driver object, with fields like firstName, and more. Inside of the ProfileTab is where I do the actual modification. Inside the parent component EditDriver is where I need to send this data to the server in the updateDriver function.
//...imports
class EditDriver extends Component {
constructor(props) {
super(props);
this.state = {
driver: {
firstName: '',
},
};
this.updateDriver = this.updateDriver.bind(this);
this.driverOnChange = this.driverOnChange.bind(this);
}
updateDriver() {
this.props.updateDriver(this.state.driver);
}
driverOnChange(data) {
this.setState({
driver: data
});
}
render() {
return (
<ViewContainer
fullWidth
title="Driver"
toolbarRight={
<Button
onClick={this.updateDriver}
>
Save
</Button>
}
>
<ProfileTab match={this.props.match} driver={this.state.driver} driverOnChange={this.driverOnChange} />}
</ViewContainer>
);
}
}
const mapDispatchToProps = dispatch => ({
updateDriver: driver => dispatch(updateDriver(driver))
});
export default connect(
null,
mapDispatchToProps,
)(withStyles(styles)(EditDriver));
and the ProfileTab code looks like this:
class ProfileTab extends Component {
constructor(props) {
super(props);
this.state = {
driver: {
firstName: '',
},
};
this.handleDriverInputChange = this.handleDriverInputChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.driver && !nextProps.isFetching) {
this.setState({
driver: {
...this.state.driver,
...nextProps.driver
}
});
}
}
handleDriverInputChange(event) {
const target = event.target;
const value = target.type == 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
driver: {
...this.state.driver,
[name]: value
}
});
}
render() {
const {
driver,
} = this.state;
return (
<div>
<TextField
name="firstName"
label="firstName"
margin="normal"
type="text"
onChange={this.handleDriverInputChange}
value={driver.firstName}
/>
</div>
);
}
}
ProfileTab.propTypes = {
driver: PropTypes.object.isRequired,
driverOnChange: PropTypes.func.isRequired,
};
export default ProfileTab;
The driverOnChange function does not actually set the state of the driver parameter in the ProfileTab. What would be the most efficient way to render the ProfileTab in the EditDriver component and have the changed parameter firstName available?
Just call driverOnChange in this method inside ProfileTab component
handleDriverInputChange(event) {
const target = event.target;
const value = target.type == 'checkbox' ? target.checked : target.value;
const name = target.name;
this.props.driverOnChange(value);
this.setState({
driver: {
...this.state.driver,
[name]: value
}
});
}
You've binded driverOnChange to EditDriver component therefore I think this in driverOnChange would refer to it even when it is being called inside ProfileTab component

Categories