So, I'm pretty new to React and wasn't able to solve this issue. I'm trying to pre-populate a form with information fetched from the database using a parent component. The child component is supposed to show the data as the default and then change states when the user edits the form.
This is how I pass the data as props (Edit 2):
componentDidMount() {
const ticketID = this.props.match.params.ticketID;
axios.get(`http://127.0.0.1:8000/api/tickets/${ticketID}`).then(res => {
this.setState({ ticket: res.data }, () => console.log(this.state.ticket));
});
}
{this.state && this.state.ticket ? (
<TicketForm
requestType="put"
ticketID={this.props.match.params.ticketID}
btnText="Update"
ticket={this.state.ticket}
/>
) : (
<div />
)}
This works fine. I'm able to get the data and console.log it, but I'm unable to set it to be the default state. The form remains blank and when I try to type something on it throws the warning: "A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa)". <--- Solved (Edit 2)
I followed this tutorial https://www.youtube.com/watch?v=IK9k9hSuYeA&t=373s.
Child component is as follows:
class TicketForm extends React.Component {
constructor(props) {
super(props);
this.state = {
name: props.ticket.name || "",
description: props.ticket.description || ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
(...)
render() {
return (
<div className="form-container">
<div className="card mb-3">
<div className="card-header">
<h2>{this.state.btnText} Ticket</h2>
</div>
<div className="card-body">
<form
onSubmit={e =>
this.onSubmit(e, this.props.requestType, this.props.ticketID)
}
>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
className="form-control form-control-lg"
placeholder="Ticket name"
value={this.state.name}
onChange={e => {
this.handleChange(e);
}}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
name="description"
className="form-control form-control-lg"
rows="3"
placeholder="Ticket Description"
value={this.state.description}
onChange={e => {
this.handleChange(e);
}}
></textarea>
</div>
(...)
)}
I spent hours trying to fix this and read a bunch of posts on here, but still wasn't able to solve this issue. Any help would be much appreciated.
Edit 1: I finally figured out why props is undefined on the constructor. When I pass ticket as props, ticket is still being fetched from the database, so the value received by the constructor is still undefined.
Edit 2: Tried using ternary operator on the parent child to make sure props were only passed on after state was fully set.
This error should only occur if the initial state for name or description is undefined, which you're not checking for in your code.
Try changing your constructor to:
constructor(props) {
super();
this.state = {
name: props.ticket.name || '',
description: props.ticket.description || '',
};
This way, if you have an undefined value for either of them, you still set an empty string as a fallback value.
You are supposed to call super() with props as an argument: super(props)
constructor(props) {
super(props); // <-- instead of super()
this.state = {
name: props.ticket.name,
description: props.ticket.description
};
...
}
I'm not sure that makes a difference, it would seem that this.state.ticket passed before is not being set correctly. Can you try console.log(this.state.ticket) and see if it is indeed an object of the form {name: ..., description: ...}?
This should work: You should understand the basics of state and props before you deep dive. When you initialize a state in a constructor, it is only initialized once. React has no way to update the component state based on props. So, in order to achieve this, they provided a hook getDerivedStateFromProps as the name suggests to derive the state from props. Hope it helps you to clear your queries.
On the other note, I would encourage you to start with React Hooks rather than class components as these errors can be easily handled using useEffect in functional components. I hope I am able to give you a better overview. Let me know if you still find any difficulties.
class TicketForm extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
description: ""
};
static getDerivedStateFromProps(props, state) {
// Stringify as to check ticket is an object. In case it is a string
// or integer you can directly, check the equality
if (JSON.stringify(props.ticket) !== JSON.stringify(state)) {
return {
name: props.ticket.name,
description: props.ticket.description
}
}
return null;
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
(...)
render() {
return (
<div className="form-container">
<div className="card mb-3">
<div className="card-header">
<h2>{this.state.btnText} Ticket</h2>
</div>
<div className="card-body">
<form
onSubmit={e =>
this.onSubmit(e, this.props.requestType, this.props.ticketID)
}
>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
className="form-control form-control-lg"
placeholder="Ticket name"
value={this.state.name}
onChange={e => {
this.handleChange(e);
}}
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
name="description"
className="form-control form-control-lg"
rows="3"
placeholder="Ticket Description"
value={this.state.description}
onChange={e => {
this.handleChange(e);
}}
></textarea>
</div>
(...)
)}
Related
I am getting the error: Uncaught TypeError: Cannot read properties of undefined (reading 'state') even though state is defined in the constructor of my React component. I get the error at the line where I set the value of the <input> to {this.state.deckName}
export class DeckForm extends React.Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`)
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<this.DeckList />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
DeckName() {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
DeckList() {
let format = 'EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3'
return (
<div className='form-group'>
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className='form-control'
rows='15'
required
>
{format}
</textarea>
</div>
);
}
}
Use below code it's working for me
https://codesandbox.io/s/weathered-water-jm1ydv?file=/src/App.js
DeckName() {
return (
<div className="form-group mb-3">
<input
value={this?.state.deckName} /* ERROR HERE */
onChange={this?.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
}
DeckList() {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this?.state.deckList}
onChange={this?.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
}
You can use es6 function to return components which exist outside of parent rather than using method,change only this part of code:
1.instead of DeckName(){...}use DeckName =()=>{....}
2.instead of DeckList(){...}use DeckList =()=>{....}
Full modified code:
import React, { Component } from "react";
export class DeckForm extends Component {
constructor(props) {
super(props);
this.state = { deckName: "", deckList: "" };
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
console.log(typed);
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`);
}
render() {
return (
<form className="was-validated">
<this.DeckName />
<this.DeckList />
<button type="submit" className="btn-lg btn-warning mt-3">
Create
</button>
</form>
);
}
DeckName = () => {
return (
<div className="form-group mb-3">
<input
value={this.state.deckName}
onChange={this.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
};
DeckList = () => {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
};
}
Live Demo:
https://codesandbox.io/s/brave-hill-l0eknx?file=/src/DeckForm.js:0-2091
Another way to solve the issue is defining DeckName() method using arrow function. Here's a code snippet I tried with react 17.0.2 which worked perfectly fine for me.
It's always recommended to use arrow function to define methods in class based components, since arrow function inherit "this" from the block its called from, so you also don't have to do .bind(this) whenever you call methods.
JSX
import React, { Component } from 'react'
class Test extends Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
}
DeckName = () => {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
}
export default Test
This question already has answers here:
How to disable button in React.js
(8 answers)
Closed 3 years ago.
I am using trying to disable a button in react based on couple states. Down below is a breakdown of my code
constructor(props) {
super(props);
this.state = {
email: '',
pass: '',
disabled: true
}
this.handleChange = this.handleChange.bind(this);
this.handlePass = this.handlePass.bind(this);
}
pretty self explanatory constructor. The disabled will be changed as state changes. My render method looks something like this
render() {
if(this.state.email && this.state.pass) {
this.setState({ disabled: false })
}
return (
<div className='container'>
<div className='top'></div>
<div className='card'>
<MuiThemeProvider>
<Card >
<div className='wrapper'>
<TextField
hintText="Email"
value={this.state.email} onChange={this.handleChange}
/><br/>
<TextField
hintText="Password"
type="password"
/><br/>
<div className='login-btn'>
<RaisedButton label="Login" primary={true}
disabled={this.state.disabled} />
</div>
</div>
</Card>
</MuiThemeProvider>
</div>
</div>
)
}
As you can see I have 2 text fields and I am handeling the data changes with the following method
handleChange(e) {
this.setState({email: e.target.value});
}
handlePass(e) {
this.setState({pass: e.target.value});
}
Now my button is initially disabled and everytime a state is changed and component re-renders I want to check for state changes and enable button accordingly. So I was thinking of using the life cycle method like so
componentWillMount() {
if(this.state.pass && this.state.disabled) {
this.setState({disabled: false})
}
}
However, this doesn't work. When both email and password field is not empty the button stays disabled. I am not sure what am I doing wrong.
Please, do not set states inside render() function. That might cause infinite loops to occur.
Refer: https://github.com/facebook/react/issues/5591
Instead of setting states inside render() function, you can set the disabled state inside the handleChange() and handlePass() function.
If more detail required, please do mention.
You should be setting the disabled state inside your handleChange and handlePass functions.
componentWillMount() only runs right before the component is rendered, but never again.
Just made a demo , is that you need, check the code in the demo below
demo
Change below code :
class App extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
pass: '',
invalidData: true
}
this.onEmailChange = this.onEmailChange.bind(this);
this.onPasswordChange = this.onPasswordChange.bind(this);
}
// componentWillUpdate is to be deprecated
//componentWillUpdate(nextProps, nextState) {
// nextState.invalidData = !(nextState.email && nextState.pass);
//}
onEmailChange(event) {
this.setState({ email: event.target.value });
}
onPasswordChange(event) {
this.setState({ pass: event.target.value });
}
render() {
return (
<form>
<input value={this.state.email} onChange={this.onEmailChange} placeholder="Email" />
<input value={this.state.password} onChange={this.onPasswordChange} placeholder="Password" />
// from this <button disabled={this.state.invalidData}>Submit</button>
//to
<button disabled={!(this.state.email && this.state.password)}>Submit</button>
</form>
);
}
}
**updated **
disable submit button in <button disabled={!(this.state.email && this.state.password)}>Submit</button> itself.
I have a usecase that there is a form which should be controlled one and should have its field be pre-populated so that user can edit the form. For this what i have done is
const mapStateToProps = createStructuredSelector({
myInfo: makeSelectMyInfo(),
errorResponse: makeSelectMyInfoErrorResponse()
});
const mapDispatchToPropes = dispatch => ({
loadMyInfo: () => dispatch(getMyInfo()),
updateMyInfo: (myInfo, token) => dispatch(updateMyInfo(myInfo, token))
});
class ConfirmPropertyByUser extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
user_info: {
contact_fname: "",
contact_lname: "",
agree_terms_condition: false,
}
};
}
componentDidMount() {
this.props.loadMyInfo();
}
componentWillReceiveProps(nextProps) {
if (nextProps.myInfo !== this.props.myInfo) {
console.log("object", Object.values(nextProps.myInfo));
this.setState(state => ({
user_info: {
...state.user_info,
contact_fname: nextProps.myInfo.contact_fname,
contact_lname: nextProps.myInfo.contact_lname,
}
}));
}
}
handleChange = e => {
this.setState({
user_info: { ...this.state.user_info, [e.target.name]: e.target.value }
});
};
handleUserTerms = e =>
this.setState({
user_info: {
...this.state.user_info,
agree_terms_condition: e.target.checked
}
});
handleSubmit = e => {
e.preventDefault();
this.props.updateMyInfo(this.state.user_info, this.props.match.params.id);
};
render() {
const { errorResponse } = this.props;
const { user_info } = this.state;
let message;
if (errorResponse && typeof errorResponse === "string") {
message = <Notification message={errorResponse} timeout={5000} />;
}
return (
<div className="container">
{message && message}
<div className="card card-lg">
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label>First Name</label>
<input
type="text"
name="contact_fname"
className="form-control"
value={user_info && user_info.contact_fname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<label>Last Name</label>
<input
type="text"
name="contact_lname"
className="form-control"
value={user_info && user_info.contact_lname}
onChange={this.handleChange}
/>
</div>
<div className="form-group">
<input
className="custom-control-input"
type="checkbox"
onChange={this.handleUserTerms}
/>
</div>
<button
className="btn btn-default btn-block btn-lg"
disabled={
!user_info.password || !user_info.agree_terms_condition
}
>
Submit Details
</button>
</fieldset>
</form>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToPropes)(
ConfirmPropertyByUser
);
I am using redux and also updating the internal state. But I have heard somewhere that when using redux its unnecessary to update the internal state. How can i approach the following problem without updating the internal state? Can anyone help me on this, please?
What you're doing appears to be fine. Per the Redux FAQ, there's nothing wrong with using component state in a Redux app. For forms, it's very common to need to have both an "original" set of values and a "work-in-progress" copied set of values, and it's up to you whether the "WIP" values are stored in Redux or in a React component.
For what it's worth, I did show some examples of putting the "WIP" form state into Redux in my blog post Practical Redux, Part 8: Form Draft Data Management, which might be a useful reference. But, overall, your code here looks good - you're correctly copying props to state in the constructor and in componentWillReceiveProps, and the conceptual approach you're following is perfectly fine.
One small stylistic suggestion: I generally recommend that people use the object shorthand syntax for the mapDispatch argument. In your case, it would look like:
const actions = {loadMyInfo : getMyInfo, updateMyInfo : updateMyInfo};
// later
export default connect(mapState, actions)(ConfirmPropertyByUser);
I have the following code, which results in the focus being lost as soon a key is pressed in the firm password field. Exploration suggests the form is being re-rendered, as a result of setState but I am not sure why or how to fix.
import React from 'react';
import LocalizedStrings from 'react-localization';
let strings = new LocalizedStrings({
// code omitted
});
class ResetPasswordForm extends React.Component {
constructor (props) {
super(props);
this.state = {
key: this.props.key,
password: '',
passwordConfirm: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
// TODO
}
renderField(field) {
const name = field.name;
const id = field.id || name;
const label = strings[name];
const fieldType = field.type || 'text';
const placeholder = strings[name + '-placeholder'];
// TODO
let touched;
let error;
return <div className="form-group">
<label htmlFor={id}>{label}:</label>
<input id={id} type={fieldType} placeholder={placeholder} name={name} onChange={this.handleChange}
value={this.state[name]}
className="form-control"/> {touched && error && <div className="error bg-warning">{error}</div>}
</div>
}
render() {
const Field = this.renderField.bind(this);
return (
<div>
<h2>{strings['title']}</h2>
<form onSubmit={this.handleSubmit} >
{this.props.statusText && <div className='alert alert-info'>
{this.props.statusText}
</div>}
<Field
name="password"
type="password"
/>
<Field
name="passwordConfirm"
type="password"/>
<div className="form-group">
<input
type="submit"
name="Submit"
defaultValue={strings['submit']}
/>
</div>
</form>
</div>
)
}
}
Edit: Further experimenting suggest that 'Field' lines are causing the issue and replacing them with the following resolves the issue:
{this.renderField({
name: "password",
type: "password"
})}
{this.renderField({
name: "passwordConfirm",
type: "password"
})}
Alternatively, in the constructor I can do:
this.renderField = this.renderField.bind(this);
and then in the render function I can do:
const Field = this.renderField;
While these approaches work, I am not sure the exact impact of the change sin terms of React. If anyone can explain, it would be appreciated.
I think here is the error:
const Field = this.renderField.bind(this);
renderField(field) accept the object parameter(field) and you never pass it. So you need to pass it
const Field = this.renderField.bind(this, { name: "password", type: "password"});
Each time render is executed, you generate new function-Component with this.renderField.bind(this). In this case, React's reconciliation algorithm will treat this as a difference, and the entire subtree will be rendered from scratch.
From docs:
Elements Of Different Types
Whenever the root elements have different types, React will tear down
the old tree and build the new tree from scratch. Going from to
, or from to , or from to - any
of those will lead to a full rebuild.
Here is a screencap of DevTools elements inspector, which illustrates DOM updates in both cases
Original (Field is generated on each render)
You can see that the <div> node is collapsed and rendered from scratch => focus is lost.
Fixed (this.renderField({...}) is used)
Only "value" field is updated, the rest of DOM is intact => focus is preserved
Consider having form component like:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.props.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
I would then use this <Form onSubmit={this.someFunction} /> component elsewhere within my app, lets assume inside HomePage component. Inside that home page I would have this.someFunction that executes when form is summited, how can I pass form value / state to it?
Create a callback in your component that will call the function sent to Form with the state as parameter.
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
What you're (essentially) looking to do is pass some data up the component chain (to a parent component). You could implement this with vanilla React, but I'm not going to advise you to do this.
If you try implementing some kind of state management yourself, unless your app is incredibly simple or you are an incredibly disciplined one-man-team, it's likely to get messy and unpredictable fast.
I advocate one way data flow. Data should flow one way through your app - down. I recommend you look at implementing a solution with Flux or Redux (Redux is my preference). These are both state containers that will propagate state throughout your app and enforce a set of conventions which you help you maintain structure to your data flow as your app grows.
I admit, you're adding to the learning curve by implementing a solution with these containers, but remember that React is only the view layer and it can't help you much with problems surrounding state management.
You could do this:
export default class Form extends React.Component {
constructor (props) {
this.state = { email: '' }
this.onChange = this.onChange.bind(this)
this.onSubmit = this.onSubmit.bind(this)
}
onChange(event) {
this.setState({ email: event.target.value })
}
// Wrap around this.props.onSubmit and add data to it.
onSubmit() {
this.props.onSubmit(this.state);
}
render () {
return (
<div>
<h2>{this.props.title}</h2>
<form className={cx('Form')} onSubmit={this.onSubmit}>
<input className={cx('Form-email')} type='email' placeholder='email' value={this.state.email} onChange={this.onChange} />
<input className={cx('Form-btn')} type='submit' value='sign up' />
</form>
</div>
)
}
}
Very similar to how you bound and use your onChange.