i'm trying to to add data to the main App through an reusable component, the problem is that when i use setState, nothing is happening.
In the main app, i m calling the BookModal and passing the
state:
state= {
books: [
{id: uuidv4(),name: 'The Kingkiller Chronicle', isbm:'5435-54',quantity: '4', price:'14.99', isBorrowed: false, remainingDays: 14},
{id: uuidv4(),name: 'Jane Eyre', isbm:'643543-21',quantity: '2', price:'19.99', isBorrowed: false, remainingDays: -3}
],
newBookModal: false,
editBookModal: false,
newBookData: {
id: '',
name: '',
isbm: '',
quantity:'',
price: '',
isBorrowed: false,
remainingDays: 14
},
editBookData: {
id: '',
name: '',
isbm: '',
quantity:'',
price: ''
}
}
And here i render the component:
<BookModal booksData={this.state}/>
In the modal:
import { Component } from 'react';
import React from 'react';
import {
Button,
Modal,
ModalHeader,
ModalBody,
FormGroup,
Label,
Input,
ModalFooter
} from 'reactstrap';
import { v4 as uuidv4 } from 'uuid';
class BookModal extends React.Component {
constructor(props) {
super(props);
this.state = {
newBookData: {
id: '',
name: '',
isbm: '',
quantity:'',
price: '',
isBorrowed: false,
remainingDays: 14
},
newBookModal: false,
}
this.openNewBookModal = this.openNewBookModal.bind(this);
this.addBook = this.addBook.bind(this)
}
async openNewBookModal () {
console.log(this.state.newBookModal);
this.setState({
newBookModal: !this.props.booksData.newBookModal//the opposite of the state
});
};
addBook () {
let { books } = this.props.booksData;
books.push(this.props.booksData.newBookData);
await this.setState({ books, newBookModal: false, newBookData: {
id: uuidv4(),
name: '',
isbm: '',
quantity:'',
price: '',
isBorrowed: false
}});
}
render() {
return(
<Modal isOpen={this.props.booksData.newBookModal} toggle={this.openNewBookModal}>
<ModalHeader toggle={this.openNewBookModal}>Add a new book</ModalHeader>
<ModalBody>
<FormGroup>
<Label for="title">Title</Label>
<Input id="title" value={this.props.booksData.newBookData.name} onChange={(e) => {
let { newBookData } = this.props.booksData;
newBookData.name = e.target.value;
this.setState({ newBookData });
}} />
</FormGroup>
<FormGroup>
<Label for="isbm">ISBM</Label>
<Input id="isbm" value={this.props.booksData.newBookData.isbm} onChange={(e) => {
let { newBookData } = this.props.booksData;
if (e.target.value === '' || e.target.value.match(/^\d{1,}(\-\d{0,2})?$/)) {
newBookData.isbm = e.target.value;
}
this.setState({ newBookData });
}} />
</FormGroup>
<FormGroup>
<Label for="quantity">Quantity</Label>
<Input id="quantity" value={this.props.booksData.newBookData.quantity} onChange={(e) => {
let { newBookData } = this.props.booksData;
if (e.target.value === '' || e.target.value.match(/^\d{1,9}?$/)) {
newBookData.quantity = e.target.value;
}
this.setState({ newBookData });
}} />
</FormGroup>
<FormGroup>
<Label for="price">Price</Label>
<Input id="price" value={this.props.booksData.newBookData.price} onChange={(e) => {
let { newBookData } = this.props.booksData;
if (e.target.value === '' || e.target.value.match(/^\d{1,}(\.\d{0,2})?$/)) {
newBookData.price = e.target.value;
}
this.setState({ newBookData });
}} />
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.addBook}>Add Book</Button>{' '}
<Button color="secondary" onClick={this.openNewBookModal}>Cancel</Button>
</ModalFooter>
</Modal>
)
}
}
export default BookModal;
The problem seems to be in addBook, because it is not aware of the state from the main App component, how can i make this work, so i can enter the books.
This code has some problems, but I think the main one is conceptual. One of the things that React encourages you to do is to find the least amount of state possible.
In your app, the App component appears to handle the application's state. It tracks the data that you'll need to display in various ways, namely the list of books. Then, as you should, you pass this data, as props, down to a child component that will handle displaying this data, in this case a modal.
Where you go wrong is what you do next. The <BookModal /> component should only care about displaying the props it's given, and yet you spend a lot of code basically storing the props in BookModal's state. Why? BookModal has everything it needs in the props it was passed.
"But," you'll say, "the modal has a form that the user will use to add a new book. How will the child component, BookModal, pass that data to the parent, App?" The answer is that it won't! App tracks the state, so App should expose a function to its children that can add a book to the state. How do you get this to the child? Pass it as a prop! The only state BookModal needs is that which will allow it to control the form components.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {books: []};
}
addBook(bookToAdd) {
// instead of using push, which mutates the state (generally a bad idea), we'll make a copy of the state books array using the ES6 spread operator and add the new book
this.setState({ books: [...this.state.books, bookToAdd ] });
}
render() {
return (
{/* you don't even really need to pass the app state down to this component if all it does is render a form, but we'll leave it for now */}
<BookModal data={this.state} addBook={this.addBook} />
);
}
}
class BookModal extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
isbn: '',
quantity: '0',
price: '0',
}
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
// call the function we have been passed as a prop with the new book and let the App component handle the change in our application state
const bookToAdd = {
title: this.state.title,
isbn: this.state.isbn,
quantity: this.state.quantity,
price: this.state.price,
}
this.props.addBook(bookToAdd);
}
render() {
return (
<Modal>
<FormGroup>
<Label for="title">Title</Label>
<Input id="title" value={this.state.title} onChange={(e) => this.setState({title: e.target.value})} />
</FormGroup>
<FormGroup>
<Label for="isbn">ISBN</Label>
<Input id="isbn" value={this.state.isbn} onChange={(e) => this.setState({isbn: e.target.value})}/>
</FormGroup>
<FormGroup>
<Label for="quantity">Quantity</Label>
<Input id="quantity" value={this.state.quantity} onChange={(e) => this.setState({quantity: e.target.value})}/>
</FormGroup>
<FormGroup>
<Label for="isbn">Price</Label>
<Input id="price" value={this.state.price} onChange={(e) => this.setState({isbn: e.target.value})}/>
</FormGroup>
<Button color="primary" onClick={this.handleSubmit}>Add Book</Button>
</Modal>
);
}
}
EDIT: I haven't worked with class components in a while, so apologies if there are minor errors here (can't believe we used to bind!). Hopefully, my main point gets across.
When I try to type in the boxes on the webpage it doesn't register that I am typing anything. I am guessing it has something to do with the handleChange or onChange, but I could use some help here. I am still pretty new to React and trying to figure it out. What am I missing here?
import React, {Component} from 'react';
import { Form } from 'semantic-ui-react';
class Assessments extends Component {
state = {assessment_name: '', assessment_type: ''}
componentDidMount() {
if (this.props.id) {
const { assessment_name, assessment_type } = this.props
this.setState({ assessment_name, assessment_type })
}
}
handleChange = (a) => {
const { name, value } = a.target
this.setState({ [name]: value })
}
handleSubmit = (a) => {
a.preventDefault()
if (this.props.id) {
const { id, history } = this.props
this.props.updateName(id, this.state, history)
this.props.toggleUpdate()
}
this.props.close()
this.setState({ assessment_name: '', assessment_type: ''})
}
close = () => this.setState({ open: false })
render() {
const { assessment_name, assessment_type } = this.state
return(
<Form onSubmit={this.handleSubmit}>
<Form.Input
name=''
value={assessment_name}
onChange={this.handleChange}
label='Assessment Name'
required
/>
<Form.Input
name='AssessmentType'
value={assessment_type}
onChange={this.handleChange}
label='Assessment Type'
required
/>
<Form.Button>Submit</Form.Button>
</Form>
)
}
}
export default Assessments;
You're not passing the right names to the Form.Input components which the handleChange function uses to update the state. They have to be 'assessment_name' and 'assessment_type' respectively to make sure the state gets updated on input change events and the new values get reflected on the fields.
<>
<Form.Input
name="assessment_name"
value={assessment_name}
onChange={this.handleChange}
label="Assessment Name"
required
/>
<Form.Input
name="assessment_type"
value={assessment_type}
onChange={this.handleChange}
label="Assessment Type"
required
/>
</>
I have exported multiple variables, but the method I'm using for storing this one does not seem to work for some reason. I have login page, which stores the correct value into "ID" as shown below
import AuthService from './AuthService';
let ID = "";
class LoginPage extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.Auth = new AuthService();
}
handleFormSubmit(e){
e.preventDefault();
this.Auth.login(this.state.username,this.state.password)
.then(res =>{
if(this.Auth.state.isA)
this.props.history.push('/AdminApp');
else if(this.Auth.state.isA === 0 && this.Auth.state.sub === 0)
{
ID = this.Auth.state.userID;
console.log(ID) // This prints the right value
this.props.history.push('/SDForm')
}
})
.catch(err =>{
alert(err);
})
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
return (
<Container>
<Col className="UCFLogo"><img src={logo} /></Col>
<Form className="LoginForm">
<Col>
<h1 className="mainTitles">Senior Design Project Selection</h1>
<h3 className="subTitle">Sign In</h3>
</Col>
<Col>
<FormGroup className="LoginPad">
<Label className="subTitle">Knights Email</Label>
<Input className="LoginInfo" type="text" name="username" id="username" onChange={this.handleChange.bind(this)} value={this.state.username} />
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label className="subTitle" for="password">Password</Label>
<Input className="LoginInfo" type="password" name="password" id="password" onChange={this.handleChange.bind(this)} value={this.state.password} />
</FormGroup>
</Col>
<Button className="subTitle" onClick={this.handleFormSubmit}>Submit</Button>
</Form>
</Container>
);
}
}
export default LoginPage;
export {ID};
Then, I need to load that ID from login into my state in my form.js file (below) in order to return it to the json upon submit, I'm just attempting to print the ID to the console until I know that I am getting the right value, and for the sake of length, I cut most of the code out, but I get this in the console
ƒ LoginPage(props) {
var _this;
Object(C_csform_master_node_modules_babel_runtime_helpers_esm_classCallCheck__WEBPACK_IMPORTED_MODULE_1__["default"])(this, LoginPage);
_this = Object(C_cs…
form.js
import ID from './LoginPage';
const Auth = new AuthService();
class SDForm extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: "",
ID: "",
}
this.Auth = new AuthService();
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
printToConsole = () => {
console.log(ID)
}
render() {
return (
<Container>
<Form className="SDForm">
// Form stuff
<Col className="subTitle">
<Button onClick={this.printToConsole}>Submit</Button>
</Col>
</Form>
</Container>
);
}
}
export default withAuth(SDForm);
This is not the proper way of passing information between components in React. In most cases, the best way to do it would be putting the value of ID in the Redux store or getting the ID value to them store it on a state and passing the ID state as a prop to the SDForm component, as shown next:
import SDForm from './SDForm.js'
And them (once you get your ID value and you store it on a state):
const { ID } = this.state;
And then in the <SDForm /> you can use ID prop as you see fit.
<SDForm id={ID} />
I am trying to create dynamic form, so I pass some jsx elements to a child component as a property. Even though the state is being updated, the updated state is not passed to the element. Below is the code:
This is the child component which maps over the passed controls and outputs them.
class Form extends Component {
render() {
return (
<div>
{this.props.controls.map((c, i) => {
return <React.Fragment key={i}>{c}</React.Fragment>;
})}
</div>
);
}
}
This is the App that calls the child component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
this.controls = [
<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>
];
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls} />
</div>
<div className="app__group">
<h1>This is working</h1>
<input
type="text"
onChange={this.onChangePassword}
value={this.state.password}
/>
</div>
</div>
);
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}
As an example of the unexpected behaviour, when an input passed as a property to the child component, I cannot type in the input. The state gets updated but it's is not passed to the child, thus the text does not show.
On the other hand, a standard input element works, I can type and see the output.
What am I doing wrong?
the problem is that you are trying to make something "fixed" as something dynamic. lets go with a more functional approach and it will refresh each one of the inputs like if they are dynamic.
class App extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
componentDidUpdate() {
console.log(this.state);
}
render() {
return (
<div className="App">
<div className="app__group">
<h1>This is not working</h1>
<Form controls={this.controls()} />
</div>
</div>
);
}
controls = () => {
return [<input
type="text"
onChange={this.onChangeUsername}
value={this.state.username}
/>]
}
onChangeUsername = e => {
console.log('onChangeUsername', e.target.value);
this.setState({ username: e.target.value });
};
onChangePassword = e => {
console.log('onChangePassword');
this.setState({ password: e.target.value });
};
}
I have a form which has 3 field. First two fields are email_from and email_subject and the last field is an editor. I have used draftjs for editor. If there is no editor i could enable and disable the submit button but due to the inclusion of the editor i did not know how can i enable the button only when all the fields are filled with no error.
Here is my code.
AdminEditor is the component which has draftjs editor
class EmailTemplate extends React.Component {
state = {
emailSubject: '',
emailFrom: ''
};
handleChange = event =>
this.setState({ [event.target.name]: event.target.value });
handleSubmit = event => {
event.preventDefault();
};
render() {
const { emailSubject, emailFrom } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<FieldGroup
id="formControlsText"
name="emailSubject"
type="text"
label="Email Subject"
placeholder="Enter Email Form"
onChange={this.handleChange}
validationState={emailSubject ? 'success' : 'error'}
required
/>
<FieldGroup
id="formControlsText"
name="emailFrom"
type="email"
label="Email From"
placeholder="Enter Email From"
onChange={this.handleChange}
validationState={emailFrom ? 'success' : 'error'}
required
/>
<AdminEditor />
<Button type="submit">
Submit
</Button>
</form>
);
}
}
export default EmailTemplate;
class AdminEditor extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
isEmpty: true
};
}
onEditorStateChange = editorState => {
this.setState({
editorState
});
};
onEditorChange = editorContent => {
this.setState({
editorContent
});
};
handleChange = event =>
this.state.editorState.getCurrentContent().hasText()
? this.setState({ isEmpty: false })
: this.setState({ isEmpty: true });
render() {
const { editorState } = this.state;
return (
<div>
<Editor
editorState={this.state.editorState}
initialContentState={rawContentState}
toolbarClassName="home-toolbar"
wrapperClassName="home-wrapper"
editorClassName="home-editor"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
textAlign: { inDropdown: true },
}}
onContentStateChange={this.onEditorChange}
placeholder="write text here..."
onChange={this.handleChange}
/>
</div>
);
}
}
export default AdminEditor;
Can anyone please help me?
There are many ways to do this, mostly based around passing some callbacks down into the controlled component which updates the state in the parent.
A simple way would be to pass a handler that sets a flag if the editor is empty or not:
class AdminEditor extends React.PureComponent {
...
handleChange = event =>
this.props.setEditorIsEmpty(
this.state.editorState.getCurrentContent().hasText()
);
Then in EmailTemplate:
setEditorIsEmpty(editorIsEmpty) {
this.setState({
editorIsEmpty
});
}
render() {
const { emailSubject, emailFrom } = this.state;
return (
<form onSubmit={this.handleSubmit}>
...
<AdminEditor
setEditorIsEmpty={this.setEditorIsEmpty}
/>
<Button
type="submit"
disabled={!emailSubject || !emailFrom || editorIsEmpty}>
Submit
</Button>
</form>
);
}