How to make 'this' refer to App, not modal - reactjs

In my EditRecipeForm component, when editing text in the modal, I get Uncaught TypeError: this.setState is not a function , because this refers to the modal window, and not the "app", the thing I don't understand is how to get this to be correct, so that editing the field value updates actually works.
(I removed code not directly related), full demo available here: http://codepen.io/pdewouters/pen/adLpvQ
const Button = ReactBootstrap.Button,
Accordion = ReactBootstrap.Accordion,
Panel = ReactBootstrap.Panel,
Modal = ReactBootstrap.Modal,
Input = ReactBootstrap.Input,
ButtonToolbar = ReactBootstrap.ButtonToolbar,
ButtonInput = ReactBootstrap.ButtonInput,
ListGroup = ReactBootstrap.ListGroup,
ListGroupItem = ReactBootstrap.ListGroupItem
class App extends React.Component {
constructor(props){
super(props)
this.state = {
showModal: false,
showEditModal: false,
recipes: [],
recipeName: '',
ingredients: '',
editRecipeNameInputVal: '',
editIngredientsInputVal: '',
recipeToEdit: {recipeName:'',ingredients:''}
}
}
closeEditModal(){
this.setState({ showEditModal: false })
}
openEditModal(recipe){
this.setState({showEditModal:true,recipeToEdit:recipe})
}
handleRecipeEditInputChange(value){
this.setState({editRecipeNameInputVal: value})
}
handleIngredientsEditInputChange(value){
this.setState({editIngredientsNameInputVal: value})
}
render(){
return(
<div className="container">
<div className="page-header">
<h1>Recipes</h1>
</div>
<div className="row">
<div className="col-md-12">
<EditRecipeForm
showModal={this.state.showEditModal}
closeModal={this.closeEditModal.bind(this)}
handleSubmit={this.handleEditFormSubmit.bind(this)}
handleRecipeEditInputChange={this.handleRecipeEditInputChange.bind(this)}
handleIngredientsEditInputChange={this.handleIngredientsEditInputChange.bind(this)}
recipe={this.state.recipeToEdit}
handleRecipeEditInputChange={this.handleRecipeEditInputChange}
handleIngredientsEditInputChange={this.handleIngredientsEditInputChange} />
<RecipeList recipes={this.state.recipes}
handleDeleteRecipe={this.handleDeleteRecipe.bind(this)}
handleOpenEditModal={this.openEditModal.bind(this)}
/>
</div>
</div>
</div>
)
}
}
class RecipeList extends React.Component {
handleOpenEditModal(recipe,event) {
this.props.handleOpenEditModal(recipe)
}
render(){
let recipes = this.props.recipes.map((recipe,index)=>{
return (
<Panel header={recipe.recipeName} eventKey={index}>
<IngredientsList ingredients={recipe.ingredients} />
<ButtonToolbar>
<Button bsStyle="danger" onClick={this.props.handleDeleteRecipe.bind(null,index)}>Delete</Button>
<Button bsStyle="primary" onClick={this.handleOpenEditModal.bind(this,recipe)}>Edit</Button>
</ButtonToolbar>
</Panel>
)
})
return (
<Accordion>
{recipes}
</Accordion>
)
}
}
class EditRecipeForm extends React.Component {
handleRecipeEditInputChange(e){
this.props.handleRecipeEditInputChange(e.target.value)
}
handleIngredientsEditInputChange(e){
this.props.handleIngredientsEditInputChange(e.target.value)
}
render(){
return(
<Modal show={this.props.showModal} onHide={this.props.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Edit recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<form onSubmit={this.props.handleSubmit}>
<Input type="text"
label="Recipe"
placeholder="blueberry pancakes"
value={this.props.recipe.recipeName}
onChange={this.handleRecipeEditInputChange.bind(this)} />
<Input type="textarea"
label="Ingredients"
placeholder="milk,sugar,flour,butter,blueberries"
value={this.props.recipe.ingredients}
onChange={this.handleIngredientsEditInputChange.bind(this)} />
<ButtonInput type="submit" value="Save changes" />
</form>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.closeModal}>Close</Button>
</Modal.Footer>
</Modal>
)
}
}
React.render(<App />, document.querySelector('.app'))

It looks like you're passing the handleRecipeEditInputChange and handleIngredientsEditInputChange prop callbacks twice and the second time you are not binding to the component instance.
<EditRecipeForm
showModal={this.state.showEditModal}
closeModal={this.closeEditModal.bind(this)}
handleSubmit={this.handleEditFormSubmit.bind(this)}
handleRecipeEditInputChange={this.handleRecipeEditInputChange.bind(this)}
handleIngredientsEditInputChange={this.handleIngredientsEditInputChange.bind(this)}
recipe={this.state.recipeToEdit}
handleRecipeEditInputChange={this.handleRecipeEditInputChange}
handleIngredientsEditInputChange={this.handleIngredientsEditInputChange} />
The second props will overwrite the first ones, so try deleting the second ones.

Related

How to add a new component with each input?

I have a form. When I enter something there and click on "submit", I want my app to add a new component which must include this one input everytime when I click on "submit".
export default class AddForm extends Component{
constructor(props){
super(props);
this.state = {
input: '',
obj: [],
}
this.onHandleChange = this.onHandleChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}
onHandleChange(e){
this.setState({
input: e.target.value
});
}
onHandleSubmit(){
this.state.obj.push(this.state.input);
this.setState({
input: ''
})
}
render(){
return(
<div className = 'adder'>
<h1 className = 'header'>Enter the type of tasks you need to be done:</h1>
<div>
<form>
<input className = 'board-add' onSubmit = {this.onHandleSubmit} onChange = {this.onHandleChange} type = 'search' name = 'textarea' placeholder = 'How shall we call the board?'/>
<p><button className = 'cancel'>CANCEL</button>
<button onClick = {this.onHandleSubmit} className = 'create'>CREATE</button></p>
</form>
</div>
{this.state.obj.map((item) => <TaskBoard taskType = {item} />)}
</div>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
this.state.obj.push(this.state.input);
^ This is not good practice in React. instead do this
obj: [...this.state.obj, this.state.input],
For more information on ... stuff check out the link below
https://medium.com/coding-at-dawn/how-to-use-the-spread-operator-in-javascript-b9e4a8b06fab
also
You did not have a value prop on the input field, thus you wont be able to reset the field after submit is executed.
<input
className="board-add"
onSubmit={this.onHandleSubmit}
onChange={this.onHandleChange}
type="search"
name="textarea"
value={this.state.input}
placeholder="How shall we call the board?"
/>
codesandbox
https://codesandbox.io/s/adoring-elbakyan-69hth?file=/src/App.js:0-1459
Hope this answers your question.
AddForm.js
import React from "react";
import "./styles.css";
import TaskBoard from "./Taskboard";
export default class Addform extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "",
arr: []
};
this.onHandleChange = this.onHandleChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}
onHandleChange(e) {
this.setState({
input: e.target.value
});
}
onHandleSubmit(e) {
e.preventDefault();
this.setState({
arr: [...this.state.arr, this.state.input],
input: ""
});
}
render() {
console.log(this.state.arr);
return (
<div className="adder">
<h1 className="header">Enter the type of tasks you need to be done:</h1>
<div>
<form>
<input
className="board-add"
onSubmit={this.onHandleSubmit}
onChange={this.onHandleChange}
type="search"
name="textarea"
value={this.state.input}
placeholder="How shall we call the board?"
/>
<p>
<button className="cancel">CANCEL</button>
<button onClick={this.onHandleSubmit} className="create">
CREATE
</button>
</p>
</form>
</div>
{this.state.arr.map(item => (
<TaskBoard taskType={item} />
))}
</div>
);
}
}
Taskboard.js
import React from "react";
export default function TaskBoard(props) {
return <div style={{ color: "tomato" }}> {props.taskType}</div>;
}

I am trying to pass a value from one component with a click function to another

My app.js creates a widget layout, within it I have an handleWidgetSelection function which should take an id when an item is clicked in my modal component. When I console log in the handleWidgetSelection function I notice that the state is not updated and it remains null as I originally set it as. I am fairly new to react so any help would be great.
This is my app.js
class App extends Component {
constructor(props) {
super(props);
this.state={
selectedWidgetId: null, //initially set as null because no widget is selected
widgetOptions:[{name:"Data Table", comp:<DataTable/>},{name:"List", comp:<CheckboxList/>}],
widgets:[ //array for layout
{id:1, content: <DataTable/>},
{id:2, content: <CheckboxList/>},
{id:3, content: ""},
{id:4, content: ""}
],
isModalOpen: false
}
}
handleWidgetSelection=(id) => {
this.setState({selectedWidgetId: id})
console.log(this.state.selectedWidgetId); //trying to fix so this value does not always print null
}
.....
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
//I am passing my values to the AddWidgetDialog component here
<AddWidgetDialog handleWidgetSelection={this.handleWidgetSelection} widgets={this.state.widgetOptions} isModalOpen={this.state.isModalOpen} onRequestClose={this.onRequestClose} />
<Grid container spacing={24}>
{
this.state.widgets.map((widget,index)=>{
return(
<Grid item xs={12} sm={6}>
<Paper className={classes.paper}><Swappable id={widget.id} content={widget.content} delete={this.deleteEvent.bind(this,index)} add={this.addEvent.bind(this,index)}/></Paper>
</Grid>
)
})
}
</Grid>
</div>
);
}
This is my AddWidgetDialog component
import React, { PropTypes } from 'react';
import Modal from 'react-modal';
const AddWidgetDialog = ({ handleWidgetSelection, widgets, isModalOpen, onRequestClose}) => {
const widgetItems = widgets.map((widget) => {
return (
<div className="list-group">
<a href="#" onClick={() => handleWidgetSelection(widget.name)} className="list-group-item">
<h6 className="list-group-item-heading">{widget.name}</h6>
</a>
</div>
);
});
return (
<Modal
className="Modal__Bootstrap modal-dialog"
isOpen={isModalOpen}>
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={onRequestClose}>
<span aria-hidden="true">×</span>
<span className="sr-only">Close</span>
</button>
<h4 className="modal-title">Add a widget</h4>
</div>
<div className="modal-body">
<h5>Pick a widget to add</h5>
{widgetItems}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" onClick={onRequestClose}>Close</button>
</div>
</div>
</Modal>
);
};
export default AddWidgetDialog;
setState maybe asynchronous. See the docs Why is setState giving me the wrong value?
handleWidgetSelection=(id) => {
this.setState({selectedWidgetId: id})
console.log(this.state.selectedWidgetId); //no guarantee state is updated or not.
}
You should use console.log() in the callback as second argument to the setState
handleWidgetSelection=(id) => {
this.setState({selectedWidgetId: id},() => {
console.log(this.state.selectedWidgetId); //state is updated now.
})
}

Modal Components using ReactStrap

Currently I have a single Component (Logbook.js) that has a table of logs, each with a unique key. When I click on the table row a modal displays the data of that row in a modal. From this modal, when I click update another modal appears and the data is neatly displayed in a form ready to update. I want to separate these modals into separate components, DisplayModal.js and UpdateModal.js. How can I do this so that the data contained in the row is carried to reach component?
This can give you an idea where to start:
const { Button, Modal, ModalHeader, ModalBody, ModalFooter } = Reactstrap;
class Modal1 extends React.Component {
render() {
return (
<React.Fragment>
<ModalHeader toggle={this.toggle}>{this.props.title}</ModalHeader>
{this.props.children}
</React.Fragment>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
modalType: 1
};
this.toggle = this.toggle.bind(this);
this.changeModalType = this.changeModalType.bind(this);
}
changeModalType(type) {
this.setState({modalType: type});
}
toggle() {
this.setState(prevState => ({
modal: !prevState.modal
}));
}
renderDisplayModal() {
return(
<React.Fragment>
<ModalBody>
This is display modal
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => this.changeModalType(2)}>Open Update Modal</Button>{' '}
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</React.Fragment>
);
}
renderUpdateModal() {
return(
<React.Fragment>
<ModalBody>
This is update modal
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={() => this.changeModalType(1)}>Open Display Modal</Button>{' '}
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</React.Fragment>
);
}
render() {
return (
<div>
<Button color="danger" onClick={this.toggle}>Open</Button>
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<Modal1 click={this.toggle} title={this.state.modalType === 1 ? 'Display title' : 'Update Modal'}>
{this.state.modalType === 1
? this.renderDisplayModal()
: this.renderUpdateModal()
}
</Modal1>
</Modal>
</div>
);
}
}
ReactDOM.render( < App / > ,
document.getElementById('root')
);
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/6.0.1/reactstrap.full.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" crossorigin="anonymous">
<div id="root" />

Reactjs component modal onclick div

I am trying to make a modal component which I can reuse, but I don't get what I am doing wrong here. The Modal is not appearing. Can anyone help me out?
Little explanation about my app.
This app is loading a JSON url and shows a list of products, which can be marked as done. If you click the div plaatjediv you should get a popup (the modal) with details info over the clicked product.
EDIT: Edited the code as suggested here. I can see the state change to true and false if I click the div, but the Modal is still not appearing.
my code
App.js
import React from 'react';
import ProductModal from './ProductModal.js';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleModal = this.toggleModal.bind(this);
this.state = {
isLoading: true,
orders: [],
dealtOrders: [],
open: false
}
}
toggleModal() {
this.setState({
open: !this.state.open
});
}
componentWillMount() {
localStorage.getItem('orders') && this.setState({
orders: JSON.parse(localStorage.getItem('orders')),
isLoading: false
})
}
componentDidMount() {
if (!localStorage.getItem('orders')){
this.fetchData();
} else {
console.log('Using data from localstorage');
}
}
fetchData() {
fetch('http://localhost:54408/api/orders/all/26-03-2018')
.then(response => response.json())
.then(parsedJSON => parsedJSON.map(product => (
{
productname: `${product.ProductName}`,
image: `${product.Image}`,
quantity: `${product.Quantity}`,
isconfirmed: `${product.IsConfirmed}`,
orderid: `${product.OrderId}`
}
)))
.then(orders => this.setState({
orders,
isLoading: false
}))
.catch(error => console.log('parsing failed', error))
}
render() {
this.handleDoneAction = event =>
{
let itemIndex = event.target.getAttribute("data-itemIndex");
let prevOrders = [...this.state.orders];
let dealtOrders = [...this.state.dealtOrders];
const itemToMoveAtLast = prevOrders.splice(itemIndex, 1);
const addToDealtOrders = dealtOrders.concat(itemToMoveAtLast);
this.setState({dealtOrders: addToDealtOrders});
this.setState({orders: prevOrders});
};
this.handleUndoAction = event =>
{
let itemIndex = event.target.getAttribute("data-itemIndex");
let orders = [...this.state.orders];
let dealtOrders = [...this.state.dealtOrders];
const undoDealtOrder = dealtOrders.splice(itemIndex, 1);
const addToOrders = orders.concat(undoDealtOrder);
this.setState({orders: addToOrders});
this.setState({dealtOrders: dealtOrders});
};
const {isLoading, orders, dealtOrders,open} = this.state;
return (
<div>
<header>
<img src="/images/header.jpg"/>
<h1>Boodschappenlijstje <button className="btn btn-sm btn-danger">Reload</button></h1>
</header>
<ProductModal open={open} />
<div className={`content ${isLoading ? 'is-loading' : ''}`}>
<div className="panel">
{
!isLoading && orders.length > 0 ? orders.map((order, index) => {
const {productname, image, quantity, orderid} = order;
return<div className="product" key={orderid}>
<div className="plaatjediv" onClick={this.toggleModal}>
<img className="img-responsive" src={image} />
</div>
<div className="productInfo">
<p>{productname}</p>
<p>Aantal: {quantity}</p>
</div>
<div className="bdone">
<button className="btn btn-lg btn-default btndone" data-itemIndex={index} onClick={this.handleDoneAction}>Done</button>
</div>
</div>
}) : null
}
</div>
<h2>Mandje</h2>
<div className="panel">
{
!isLoading && dealtOrders.length > 0 ? dealtOrders.map((dorder, index) => {
const {productname, image, quantity, orderid} = dorder;
return<div className="productDone" key={index}>
<div className="plaatjediv">
<img className="img-responsive" src={image} />
</div>
<div className="productInfo">
<p>{productname}</p>
<p>Aantal: {quantity}</p>
</div>
<div className="bdone">
<button className="btn btn-lg btn-default btndone" data-itemIndex={index} onClick={this.handleUndoAction}>Undo</button>
</div>
</div>
}) : null
}
</div>
<div className="loader">
<div className="icon"></div>
</div>
</div>
</div>
);
}
} export default App;
ProductModal.js
import React from 'react';
class ProductModal extends React.Component {
constructor() {
super();
}
render() {
const open = this.props.open;
return (
<div className={'modal fade'+(open ? '' : 'hide')} tabindex="-1" role="dialog">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 className="modal-title">test</h4>
</div>
<div className="modal-body">
test
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" className="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
)
}
}
export default ProductModal;
I am unsure what your issue is from your question but I am guessing your model doesn't open?
When you set state, you need to set it to the opposite of this.state.open
You can do it like this:
toggleModal() {
this.setState({
open: !this.state.open
});
}
I can't see where the modal is supposed to be rendered. You have to add it to render function of your "App" class. like this:
render() {
...
return(
<ProductModal open={true} />
...
):
}
and also, in your toggleModal function, do something like this:
this.setState({ open: !this.state.open});
Hope this solves the issue.
The issue is that you do not have your <ProductModal /> as a component in your <App /> In addition to setting your open state, once shown, it will (or should) never hide because you will not be able to toggle it again using your button, and you also do not have any keybindings within your <ProductModal /> itself.
I would suggest you bind an event listener within <ProductModal /> to
Check is ESC key is pressed
Bind a Cancel/Close button (in addition to a header x button).
Listen for if anywhere outside of your dialog is clicked, dismiss the modal.
You will also need to pass a handler from <App /> down to <ProductModal /> to notify when the modal has been closed.
In your App.js
handleClose() {
this.setState({
open: false
});
}
render() {
return (
...
<ProductModal open={this.state.open} handleClose={this.handleClose.bind(this)} />
)
}
Then in your ProductModal.js
handleClose() {
this.props.handleClose();
}
Observe the following using my sandbox:
https://stackblitz.com/edit/react-98m4cr
You'll see that I've implemented the handleClose event to control the state back up to the parent. In addition, you may want to add listeners as mentioned above, all triggering handleClose in the end; just remember to unbind them in ProductModal.js componentWillUnmount.

Modal isn't opening as expected

class Posts extends Component {
constructor(){
super();
this.state = {
modalIsOpen:false
};
this.openModal = this.openModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
console.log("got here?")
this.setState({modalIsOpen: true});
}
afterOpenModal() {
// references are now sync'd and can be accessed.
this.subtitle.style.color = '#f00';
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
const keys = generateKey(new Date().getTime())
var dictionary = this.props.posts
const postItemsArr = Object.keys(dictionary).map(post=>dictionary[post])
const number = 0
const postItems = postItemsArr.map(
post=>(
<Jumbotron key={generateKey(post.positiontitle) + generateKey(post.businessId)} >
<div className="position">{post.positiontitle}</div><br></br>
<BusinessName businessnameType={post.businessname} /><br></br>
<JobDescription jobDescription={post.description_sanitized} /><br></br>
<p>
<Modal isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Example"
>
<h2 ref={subtitle => this.subtitle = subtitle}>Hello</h2>
<button onClick={this.closeModal}>close</button>
<div>I am a modal</div>
<form>
<input />
<button>tab navigation</button>
<button>stays</button>
<button>inside</button>
<button>the modal</button>
</form>
</Modal>
<button onClick={this.openModal}>Open Modal</button>
</p>
</Jumbotron>
)
)
return (
<div>
<h1> Jobs Listings </h1>
{postItems }
</div>
);
}
}
Inside my Jumbotron, my modal doesn't seem to be opening, why?
It looks like it gets to the state openModal()
but doesn't actually open the modal when the user clicks on
<button onClick={this.openModal}>Open Modal</button>
Also; should I create a separate component called Modal; what would be best practices?
Overall I'm trying to trigger the modal object for a respective list item in the list.
I would create a new presentational component called modal, then use that when you need it, rather than do what you're trying to do now, which is pass an open/close state. The Modal can be a simple presentational component that just accepts props. By doing it this way, you create reusability.
function Modal(props) {
return (
<div className={props.css.awesomeLayout}>
{props.aProp}
</div>
)
}

Resources