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);
Related
I am very new to react and working on some basics where I came up in the situation - I want to set state immediately after API call.
Scenario:
2 Forms:
1st form => accepts id and calls api to get data of single user
2nd form => updates data
PROBLEM: I want to set state when I get data after clicking submit button on 1st Form
import React, { Component, useEffect } from 'react'
import { connect } from 'react-redux';
import { getSingleUser } from '../redux/user/userActions';
export class UsersContainerUpdate extends Component {
constructor(props) {
super(props);
console.log(props.propFirstName);
this.state = {
id: '',
// first_name: props.propFirstName === '' ? '' : props.propFirstName,
first_name: props.propFirstName,
last_name: props.propLastName === '' ? '' : props.propLastName,
phone: props.propPhone === '' ? '' : props.propPhone,
email: props.propEmail === '' ? '' : props.propEmail,
address: props.propAddress === '' ? '' : props.propAddress,
city: props.propCity === '' ? '' : props.propCity,
state: props.propState === '' ? '' : props.propState,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
handleChange = (field, event) => {
this.setState({ [field]: event.target.value });
}
handleSubmit(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
id
} = this.state;
const postData = {
id: id
};
// console.log(this.state);
// console.log(postData);
this.props.getSingleUserData(id);
// if (this.props.getSingleUserData(id)) {
// this.setState({
// ...this.state,
// first_name: this.props.propFirstName
// });
// }
}
handleUpdate(event) {
// alert('A name was submitted: ' + this.state.name);
event.preventDefault();
const {
first_name,
last_name,
phone,
email,
address,
city,
state
} = this.state;
const postData = {
first_name: first_name,
last_name: last_name,
phone: phone,
email: email,
address: address,
city: city,
state: state
};
console.log(this.state);
console.log("POSTDATA:", postData);
// alert('hi');
// this.props.updateUserData(id,postData);
}
render() {
return (
<div>
<h1>Update User By ID</h1>
<form onSubmit={this.handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={this.state.id}
onChange={(event, newValue) => this.handleChange('id', event)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
<h1>Update User</h1>
<form onSubmit={this.handleUpdate}>
<div>
<label>First Name:</label>
<input
type="text"
value={this.state.first_name || this.props.propFirstName}
onChange={(event, newValue) => this.handleChange('first_name', event)}
/>
</div>
<div>
<label>Last Name:</label>
<input
type="text"
value={this.state.last_name || this.props.propLastName}
onChange={(event, newValue) => this.handleChange('last_name', event)} />
</div>
<div>
<label>Phone:</label>
<input
type="text"
value={this.state.phone || this.props.propPhone}
onChange={(event, newValue) => this.handleChange('phone', event)} />
</div>
<div>
<label>Email:</label>
<input
type="text"
value={this.state.email || this.props.propEmail}
onChange={(event, newValue) => this.handleChange('email', event)} />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
<div>
Notice Message : {this.props.propFirstName}
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
console.log(state.user);
return {
propFirstName: state.user.first_name,
propLastName: state.user.last_name,
propPhone: state.user.phone,
propEmail: state.user.email,
propAddress: state.user.address,
propCity: state.user.city,
propState: state.user.state
}
}
const mapDispatchToProps = dispatch => {
return {
getSingleUserData: id => dispatch(getSingleUser(id)),
// updateUserData: (id,postData) => dispatch(updateUser(id,postData))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(UsersContainerUpdate)
The console outputs are
The console output of line:81 is the current state which is currently empty. I want to set it there.
Thanks in advance. Cheers!!
If your requirement is just to state after API call inside this.props.getSingleUserData(id),
Approach 1: (Unclean)
Add one more argument to getSingleUserData(id, setState) and pass it this.setState as an argument and inside getSingleUserData you can set the state using the function reference passed
Approach 2:
You can return a promise from getSingleUserData and do setState once it is resolves
Suggestion:
Divide your big component into individual components (like one for getting user ID and one for User data updation). The more we identify and split our project into meanigfull individual components we get more clean codes. Also when you choose to move towards functional components you can reduce lot of boiler plates with hooks.
Problem
state.user is used to set the initial value of your component's state. Changes to those props do not change your state after the component is created. They do change the values in your inputs because the initial value was an empty string '' so you default to showing the value from props. This is very misleading since those inputs don't reflect the current state.
I bet I could delete at least half of this code but that's besides the point. But take a moment to think about why props.propState === '' ? '' : props.propState is always exactly the same as just props.propState.
Solution
I have two key recommendations for how I would rewrite this:
Select user by id
Separate into multiple components
Store only the modifications in the state
Create a selector function selectUserById that selects a user from your Redux state by the id. I don't think it makes sense to store the current user properties as top-level properties of state.user like you have them right now. It seems like you also have a property state.user.users which is an array of all loaded users so I would use that.
const selectUserById = (state, id) => state.user.users.find(user => user.id === id);
Or better yet, store an object of users keyed by id.
const selectUserById = (state, id) => state.user.users[id];
With this approach we either have a complete user object or we have undefined. It's easy to check for undefined and not show the "Update User" form at all until we have real data. This makes more sense than using empty strings as the default.
We can access the complete user object from Redux. I would not duplicate that object in state. Instead I would use the state only for the properties that you have changed. You would start out with the state as an empty object and add properties to it as you modify their inputs. You can always combine the two together using object spreading.
const merged = {...existing, ...changes}
Can you implement these suggestions using class components and connect? Yes. But why add the extra hurdle? Some of the things in your code like this.handleChange.bind(this) are relics of the past when we had to do that because there wasn't a better way. But now we have better ways so you should use them.
Code
Interactive Demo on CodeSandbox
import "./App.css";
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "../store";
import { getSingleUser, updateUser } from "../store/slice";
const selectUserById = (state, id) => state.user.users[id];
const UserIdForm = ({ submitId }) => {
const [id, setId] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
submitId(id);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>ID:</label>
<input
type="text"
value={id}
onChange={(event) => setId(event.target.value)}
/>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UpdateUserForm = ({ id }) => {
const [changes, setChanges] = useState > {};
const existing = useSelector((state) => selectUserById(state, id));
const dispatch = useDispatch();
// respond to changes in id by clearing the changes state and requesting the user
useEffect(() => {
dispatch(getSingleUser(id));
setChanges({});
}, [dispatch, setChanges, id]);
if (!existing) {
return <div>Loading User...</div>;
}
const merged = { ...existing, ...changes };
const handleChange = (property, event) => {
// in function components you have to copy the whole state
setChanges((prevChanges) => ({
...prevChanges,
[property]: event.target.value
}));
};
const handleUpdate = (event) => {
event.preventDefault();
const postData = { ...merged, id };
console.log("POSTDATA:", postData);
dispatch(updateUser(postData));
};
const renderInput = (property, label) => {
return (
<div>
<label>
{label}
<input
type="text"
value={merged[property]} // shows the current value or the updated value
onChange={(event) => handleChange(property, event)}
/>
</label>
</div>
);
};
return (
<form onSubmit={handleUpdate}>
{renderInput("first_name", "First Name:")}
{renderInput("last_name", "Last Name:")}
{renderInput("phone", "Phone:")}
{renderInput("email", "Email:")}
<div>
<input type="submit" value="Submit" />
</div>
</form>
);
};
const UsersContainerUpdate = () => {
// this is the id that was last submitted.
const [id, setId] = useState();
return (
<div>
<div>
<h1>Update User By ID</h1>
<UserIdForm submitId={setId} />
</div>
{!!id && ( // only load when there is an actual id
<div>
<h1>Update User</h1>
<UpdateUserForm id={id} />
</div>
)}
</div>
);
};
export default UsersContainerUpdate;
I'm passing information from Component A from the Component B. After that depending on the props id I'm calling an API and setting the data to states. However, when I called the setState parameter to set the the API loaded data, the API were been called contentiously. Here's the Component B code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Modal from "react-bootstrap/Modal";
import PropTypes from 'prop-types'
import axios from 'axios';
import TextBox from '../../layouts/textBox'
import { getPermission } from '../../actions/permissionActions';
class Form extends Component {
state = {
editSelectedPermissions: []
}
async componentDidMount() {
this.props.getPermission();
}
async componentDidUpdate() {
const roleId = this.getRoleId();
if (roleId) {
const res = await axios.get(`http://localhost:3000/v1/permissions/role/${roleId}/`);
console.log(res.data.data);
if ( res.data.data.permission.length != 0) {
this.setState({
editSelectedPermissions: res.data.data.permission
})
}
}
}
getRoleId=()=> this.props.data.id
render() {
const { onCloseModal, onSubmit, onChange, onCheckBox, permissions } = this.props;
const { showModal, id, name, description} = this.props.data;
const { editSelectedPermissions } = this.state;
let selectedPermission = false;
return (
<div>
<Modal show={showModal} centered onHide={onCloseModal}>
<Modal.Header closeButton>{id ? "Edit" : "Add"} User Role</Modal.Header>
<Modal.Body>
<form onSubmit={onSubmit.bind(this)}>
<input type="hidden" name="id" value={id} />
<div className="form-row">
<div className="col-md-6">
<TextBox type="text" name="name" placeholder="Enter Name" label="Name" value={name} onChange={onChange} />
</div>
<div className="col-md-6">
<TextBox type="text" name="description" placeholder="Enter Description" label="Description" value={description} onChange={onChange} />
</div>
</div>
{permissions.map((item, index) => {
if (editSelectedPermissions.length > 0)
selectedPermission = editSelectedPermissions.find((item2) => item2.id === item.id)
return (
<div className="form-check" key={index}>
<input className="form-check-input" type="checkbox" name="permission" checked={selectedPermission} onChange={onCheckBox} value={item.id}/>
<label className="form-check-label" htmlFor="defaultCheck1">
{item.name}
</label>
</div>
)
})}
<div className="d-flex justify-content-center">
<input
type="submit"
className="btn btn-primary"
value={id ? "Edit Record" : "Create Record"}
/>
</div>
</form>
</Modal.Body>
</Modal>
</div>
);
}
}
Form.propTypes = {
getPermission: PropTypes.func.isRequired,
}
const mapStateToProps = (state) => ({
permissions: state.permission.permissions
});
export default connect(mapStateToProps, {getPermission} )(Form);
Any reason why it's been called continuously?
componentDidUpdate run each time state or props change. Because you setState inside, after it it will run again, change state again, and run again infinitely. Add checker before setState
if ( res.data.data.permission.length != 0 && this.state.editSelectedPermisssions != res.data.data.premission) {
this.setState({
editSelectedPermissions: res.data.data.permission
})
}
Call API in componentDidMount cycle rather than in componentDidUpdate.
It's because
if (roleId) //always true
this statement is always true.
Maybe you could store current roleId and and do the comparision
if (this.state.currentRoleId !== roleId) {
const res = await axios.get(`http://localhost:3000/v1/permissions/role/${roleId}/`);
console.log(res.data.data);
if ( res.data.data.permission.length != 0) {
this.setState({
currentRoleId: roleId,
editSelectedPermissions: res.data.data.permission
})
}
}
It is simply because your component update frequently as such it keeps making the API call and this is because you are making the API call in componentDidUpdate. Usually, you make the API call in componentDidMount, this will make the API call once.
async componentDidMount() {
this.props.getPermission();
const res = await axios.get(`http://localhost:3000/v1/permissions/role/${roleId}/`); // should be make here
}
Not in
componentDidUpdate(){
//Avoid Making API calls here
}
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>
(...)
)}
I working on a small personal project using React on Rails. I am very new to both of these things.
I have a react component that is a form. I also have another component that has some inputs that the user can add as many as needed. Using the Add Properties button. I am trying to save the state of each input that is added. I could have the component itself save the state but how then would I send it with my fetch post request that happens onClick?
I have looked at react's context API but cant figure out if this would help me. Also I have never used redux so it is possible I should look into that as well.
https://reactjs.org/docs/context.html
https://redux.js.org/basics/usagewithreact
I understand that I don't want to reach into state. So I was trying to figure out how to create an array of objects that will hold the input values of each input pair. But I cannot seem to wrap my mind around how to implement.
class ProductsCreate extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
upc: '',
availableOn: '',
inputs: []
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
if (e.target.name === 'name') {
this.setState({ name: e.target.value });
}
if (e.target.name === 'upc') {
this.setState({ upc: e.target.value });
}
if (e.target.name === 'date') {
this.setState({ availableOn: e.target.value });
}
}
submitData = () => {
fetch(`/send_data`, {
method: 'POST',
body: JSON.stringify({
name: this.state.name,
upc: this.state.upc,
availableOn: this.state.availableOn
}),
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin'
})
.then(response => {
return response.json;
})
.then(data => {
console.log(data);
});
};
clickHandler = e => {
e.preventDefault();
this.submitData();
};
appendInput = e => {
e.preventDefault();
const newInput = `input-${this.state.inputs.length}`;
this.setState({ inputs: this.state.inputs.concat([newInput]) });
};
render() {
return (
<div className="form_container">
<h1>Products</h1>
<form>
<label>Name</label>
<input type="text" name="name" onChange={this.handleChange} />
<label>UPC</label>
<input type="text" name="upc" onChange={this.handleChange} />
<label>Availiable On</label>
<input
type="text"
name="date"
placeholder="mm/dd/yyyy"
onChange={this.handleChange}
/>
<h1>Properties</h1>
{this.state.inputs.map(input => (
<Properties key={input} />
))}
<button onClick={this.appendInput}>Add Properties</button>
<button onClick={this.clickHandler}>Save</button>
</form>
</div>
);
}
}
export default ProductsCreate;
This is the component that will be added on click
import React from 'react';
class Properties extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="property_container">
<label>Property Name</label>
<input type="text" name="propertyName" />
<label>Property Value</label>
<input type="text" name="propertyValue" />
</div>
);
}
}
export default Properties;
Pass handle change as prop to Properties component and then use that prop in input onChange within your Properties component. Make the following edits. I also suggest if you don't want to continously update the state on every character typed use debounce.
ProductsCreate
{this.state.inputs.map(input => (
<Properties key={input} onChange={this.handleChange} name={input}/>
))}
Properties
<input type="text" name={this.props.name} onChange={this.props.onChange}/>
I'm only trying to deal with the React, so my question may seem very simple, but still I'm stuck on it.
I have two blocks (div.user-data__row) in which there are some values. I change the state of the component (handleChange function), the state in which these blocks are replaced by text fields (textarea.text), and I want when I click on the save button and call saveChange function, the value from each text field is taken and passed to the blocks (1st textarea to 1st block, etc).
I found examples of solving a similar case using the ref attribute, but later read that this is no longer an actual solution and so no one does. Please help me find the actual implementation path.
class UserData extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: true,
};
this.handleChange = this.handleChange.bind(this);
this.saveChange = this.handleChange.bind(this);
}
handleChange() {
this.setState(() => ({
edit: !this.state.edit,
}));
}
saveChange() {
this.setState(() => ({
edit: false
}))
}
render() {
if (this.state.edit) {
return (
<div className="user-data">
<div className="user-data__row">{this.props.userData.name}</div>
<div className="user-data__row">{this.props.userData.email}</div>
<button onClick={ this.handleChange }>Edit</button>
</div>
);
} else {
return (
<div className="user-data">
<textarea className="text" defaultValue={this.props.userData.name}></textarea>
<textarea className="text" defaultValue={this.props.userData.email}></textarea>
<button onClick={ this.saveChange }>Save</button>
</div>
)
}
}
}
Because props are read-only & because userData (email+name) can be changed inside the component , you have to populate props values in the state, then manage the state after that will be enough.
Also , you will need to convert your textarea from uncontrolled component to controlled component by:
Using value instead of defaultValue
Implementing onChange with setState of that value as handler .
Value of textarea should be read from state not props.
If props of <UserData /> may be updated from outside throughout its lifecycle , you will need componentWillReceiveProps later on.
Also you have a typo if (!this.state.edit) { and not if (this.state.edit) { .
class UserData extends React.Component {
state = {
edit: true,
userDataName: this.props.userData.name, // Populate props values
userDataEmail: this.props.userData.email, // Populate props values
};
handleChange = () => {
this.setState((state) => ({
edit: !state.edit,
}));
}
saveChange =() => {
this.setState(() => ({
edit: false
}))
}
render() {
if (!this.state.edit) {
return (
<div className="user-data">
<div className="user-data__row">{this.state.userDataName}</div>
<div className="user-data__row">{this.state.userDataEmail}</div>
<button onClick={ this.handleChange }>Edit</button>
</div>
);
} else {
return (
<div className="user-data">
<textarea className="text" value={this.state.userDataName} onChange={(e) => this.setState({userDataName: e.target.value})}></textarea>
<textarea className="text" value={this.state.userDataEmail} onChange={(e) => this.setState({userDataEmail: e.target.value})}></textarea>
<button onClick={ this.saveChange }>Save</button>
</div>
)
}
}
}
ReactDOM.render(<UserData userData={{name: 'Abdennoor', email: 'abc#mail.com'}} /> , document.querySelector('.app'))
<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 class="app" />
You're receiving values for <div> from this.props, it means that these values should came from some external source and passed to your component. A way of passing these values to component's props is out of scope of this question, it can be implemented in a very different ways. Usually it came either from parent component or from some connected store.
You need to obtain values from your <textarea> form fields, it can be done directly (using ref) or by using some third-party library that provides form handling. Then these values needs to be stored (and obtained) either directly from component's state or from external source (via props).
Unfortunately scope of your question is too broad to be able to give more precise answer, but hope that this information will lead you to some kind of solution.
You can also use contentEditable, which it will allow you to edit the content of the div.
class UserData extends React.Component {
constructor(props) {
super(props);
this.state = {
edit: true
};
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
this.setState(() => ({
edit: !this.state.edit
}));
}
render() {
const { edit } = this.state;
return (
<div className="user-data">
<div className="user-data__row" contentEditable={edit}>{this.props.userData.name}</div>
<div className="user-data__row" contentEditable={edit}>{this.props.userData.email}</div>
<button onClick={this.handleChange}>
{edit ? Save : Edit}
</button>
</div>
)
}
}