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} />
So I have an array that gets pulled from a database and I have reversed it so that the newest db entry goes to the top when you click a button on the page which opens a little modal style window the displayed array flips and goes from oldest to newest for some reason and I don't know why.
here is the code:
import React, { Component } from 'react';
import axios from 'axios';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';
import { Modal, ModalHeader, ModalTitle, ModalClose, ModalFooter, ModalBody } from 'react-modal-bootstrap';
import TextField from 'material-ui/TextField';
class ViewNoteDetails extends Component {
constructor(props) {
super(props);
this.NewNote = this.NewNote.bind(this);
this.handleChange = this.handleChange.bind(this);
}
state = {
Case: this.props.Case,
notesToShow: this.props.Case.slice(0, parseInt(this.props.AmountToShow, 10)),
isOpen: false,
note: '',
}
NewNote() {
const self = this;
if (this.state.note !== '') {
axios.post('http://******'
+ window.location.href.split('view/')[1]
+ '/' + self.state.note
).then(function (res) {
if (res.data === "Updated") {
self.hideModal();
window.location.reload();
}
})
.catch(function (error) {
console.log(error);
});
} else {
window.alert("Cannot Enter A Blank Note")
}
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
openModal = () => {
this.setState({
isOpen: true
});
};
hideModal = () => {
this.setState({
isOpen: false
});
};
render() {
const pullRight = {
float: "right",
display: "inline",
}
const inline = {
display: "inline",
}
const Overflow = {
overflow: "auto",
width: "100%",
height: "50vw",
}
return (
<MuiThemeProvider>
<div>
<Modal isOpen={this.state.isOpen} onRequestHide={this.hideModal}>
<ModalHeader>
<ModalClose onClick={this.hideModal} />
<ModalTitle>Enter Your Note</ModalTitle>
</ModalHeader>
<ModalBody>
<TextField hintText="Enter Note" name="note" fullWidth={true} onChange={this.handleChange} />
</ModalBody>
<ModalFooter>
<RaisedButton label="Save Note" secondary={true} onClick={() => this.NewNote()} />
<RaisedButton label="Cancel Note" secondary={true} onClick={() => this.hideModal()} />
</ModalFooter>
</Modal>
<br />
<h1 id="TOP" style={inline}>Note Details</h1>
<RaisedButton label="New Note" style={pullRight} secondary={true} onClick={() => this.openModal()} />
<br />
<br />
<div style={Overflow}>
{this.state.notesToShow.reverse().map(function (note, index) {
return (
<div key={note.NotePK}>
<p className="well well-lg">
{note.CreationDate.split('T')[0]} # {note.CreationDate.split('T')[1].split('.')[0]} : {note.Note}
</p>
</div>
);
})}
</div>
<br />
</div>
</MuiThemeProvider>
);
}
}
***** is in replacement of the database location
when you click the new note button the order reverses on the rendered notes
so if it was:
note 1
note 2
note 3
on click of new note it would change to
note 3
note 2
note 1
why does it do this and how do i fix this.
Don't reverse it in your render method. Every time it gets re-rendered you will reverse it. So what happens is probably is that it is getting reversed a second time when you open the modal window.
Do it here for once
state = {
Case: this.props.Case,
notesToShow: this.props.Case.slice(0, parseInt(this.props.AmountToShow, 10)).reverse(),
isOpen: false,
note: '',
}
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>
);
}