Changing the state from reusable component - javascript

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.

Related

React on Rails: Can't enter text in my form input boxes

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
/>
</>

How to pass a variable from one page to another in ReactJS?

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} />

react state is not updated/passed to child component when child component is passed as a property

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 });
};
}

How to bind the elements to the modal in React?

I am using rendering elements from dynamodb using serverless and showing in a card dashboard and when i am editing the card i want to see the contents that are already present in the card .
Edit.js
import React, { Component } from "react";
import { Form, Modal, Button, Container, Icon } from "semantic-ui-react";
import Amplify, { API } from "aws-amplify";
const uuidv1 = require("uuid/v1");
class EditItemModal extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.state = { item: this.props.item };
}
deleteItem() {
let apiName = "ServerlessReactExampleCRUD";
let path = "/ServerlessReactExample/object/" + this.props.item[0].ID;
API.del(apiName, path)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error.response);
});
this.props.getItems();
this.handleClose();
}
handleChange(event, { name, value }) {
this.setState({ [name]: value });
}
handleSubmit(event) {
let apiName = "ServerlessReactExampleCRUD";
let path = "/ServerlessReactExample";
let editItem = {
body: {
ID: this.props.item[0].ID,
name: this.state.name,
price: this.state.price,
description: this.state.description
}
};
API.put(apiName, path, editItem)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error.response);
});
this.props.getItems();
this.handleClose();
event.preventDefault();
}
handleOpen = () => this.setState({ modalOpen: true });
handleClose = () => this.setState({ modalOpen: false });
render() {
return (
<Container style={{ padding: 10 }}>
<Modal
trigger={
<Button icon onClick={this.handleOpen}>
<Icon name="edit" />
</Button>
}
open={this.state.modalOpen}
closeIcon
onClose={this.handleClose}
>
<Modal.Header>Edit</Modal.Header>
<Modal.Content>
<Form onSubmit={this.handleSubmit}>
<Form.Group unstackable widths={2}>
<Form.Input
name="name"
label="Item Name"
placeholder="Edit Item Name..."
onChange={this.handleChange}
value={this.state.name}
/>
<Form.Input
name="price"
label="Item Price"
placeholder="£0.00"
onChange={this.handleChange}
value={this.state.price}
/>
</Form.Group>
<Form.TextArea
name="description"
label="Item Description"
placeholder="Edit Description of the Item..."
onChange={this.handleChange}
value={this.state.description}
/>
<Form.Button type="submit">Submit</Form.Button>
</Form>
</Modal.Content>
<Modal.Actions>
<Button icon labelPosition="left" onClick={this.deleteItem}>
<Icon name="delete" />
Delete Item
</Button>
</Modal.Actions>
</Modal>
</Container>
);
}
}
export default EditItemModal;
This is how the card looks like
When i clicked on edit button it will look like this
What i want to show the values that are present in the card ? I want to know how i can bind them to show in the card ?
Any help is appreciated.
Ref : Ref links
As Simão wrote you're probably accessing wrong element
this.state = { item: this.props.item };
creates this.state.item (with your values/properties inside ?)
If you don't want to change everything probably this will fix it:
this.state = { ...this.props.item };
I would move operations into parent to keep all (CRUD) in one place. Updating/deleting item on list in handlers could avoid unnecessary list reloading (getItems) and done before api call would work as optimistic update.

How to render a form with Users information ( React + Redux )

I've managed to get the users information using axios request. I've also managed to render users info on a view using a component that i created. My problem is that i cant display it on an another component i have created which is called ProfileForm. ProfileForm is used as form for updating the info of the user. I want to set the state on the constractor with the user info.
Also, when i change the values to this.props.user.username etc.. i receive two errors:
Warning: Failed prop type: The prop value is marked as required in
TextFieldGroup, but its value is undefined.
in TextFieldGroup (at ProfileForm.js:112)
in ProfileForm (at ProfilePage.js:23)
and the second is
warning.js:36 Warning: TextFieldGroup is changing an uncontrolled
input of type text to be controlled. Input elements should not switch
from uncontrolled to controlled (or vice versa). Decide between using
a controlled or uncontrolled input element for the lifetime of the
component.
class ProfileForm extends React.Component
constructor(props) {
super(props);
this.state = {
username: '',
email: '',
password: '',
passwordConfirmation: '',
errors: {},
isLoading: false,
invalid: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
render() {
const { errors } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<TextFieldGroup
error={errors.username}
placeholder="Username"
onChange={this.handleChange}
value={user.username} <-- Here should be the bind
field="username"
/>
<TextFieldGroup
error={errors.email}
placeholder="Email"
onChange={this.handleChange}
checkUserExists={this.checkUserExists}
value={user.email} <-- Here should be the bind
field="email"
/>
<TextFieldGroup
error={errors.password}
placeholder="Password"
onChange={this.handleChange}
value={user.password} <-- Here should be the bind
field="password"
/>
<div className="form-group">
<button disabled={this.state.isLoading || this.state.invalid} className="btn btn-primary btn-md btn-block">Update</button>
</div>
</form>
);
}
}
export default ProfileForm;
Here is my ProfilePage
class ProfilePage extends React.Component {
componentDidMount() {
this.props.getUser();
}
render() {
const { profileUpdateRequest, addFlashMessage, getUser, isUserExists } = this.props;
return (
<div className="row">
<div className="row text-center">
<UserProfile user={this.props.user} /> <-- This works!!!!
</div>
<ProfileForm
profileUpdateRequest={profileUpdateRequest}
addFlashMessage={addFlashMessage}
getUser={getUser}
user={this.props.user} <--- This doesnt work!!!!
isUserExists={isUserExists}
/>
</div>
);
}
}
and my UserProfile which works
export default function UserProfile({ user }) {
const userProfile = (
<div className="row">
{user.username} {user.email}
</div>
);
return (
<div>
{userProfile}
</div>
);
}
My ProfileUpdateAction action
export function getUser() {
return dispatch => {
return axios.get('/api/user')
.then(res => res.data)
.then(data => dispatch(setUser(data.user)));
}
}
And my reducer
import { SET_USER } from '../actions/profileUpdateActions';
export default function user(state = [], action = {}) {
switch(action.type) {
case SET_USER:
return action.user;
default: return state;
}
}
My textFieldGroup
const TextFieldGroup = ({ field, value, label, error, type, onChange, placeholder, min, checkUserExists, disabled }) => {
return (
<div className={classnames("form-group", {'has-error': error})}>
<label className="control-label">{label}</label>
<input
type={type}
name={field}
className="form-control"
value={value}
min={min}
onChange={onChange}
onBlur={checkUserExists}
placeholder={placeholder}
disabled={disabled}
/>
{error && <span className="help-block">{error}</span>}
</div>
);
}
TextFieldGroup.propTypes = {
field: React.PropTypes.string.isRequired,
value: React.PropTypes.string.isRequired,
label: React.PropTypes.string,
error: React.PropTypes.string,
min: React.PropTypes.string,
disabled: React.PropTypes.bool,
placeholder: React.PropTypes.string.isRequired,
type: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
checkUserExists: React.PropTypes.func
}
TextFieldGroup.defaultProps = {
type: 'text'
}
export default TextFieldGroup;
In UserProfile, you're using a stateless component that is passed in props as an argument. In the function params, you destructure user to be its own constant. Thats cool and works well.
However, in UserForm, you have a class-based component. The props object is attached to the object's context (this). So to access it, you need to use this.props.user.

Categories

Resources