New to React/Redux, I am having hard time implementing on event handling.
I know that the 'this' reference key goes null when passed into the map (this.props.addRecipe.map of recipebox) function but I don't how to resolve it.
Essentially I would like to pass the onChange handler to ModalBox for each element in the array.
src/containers/recipebox
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ListGroup, ListGroupItem, Panel, Button, Modals } from 'react-bootstrap';
import MyModal from '../components/mymodal';
import { bindActionCreators } from 'redux';
import { deleteRecipe } from '../actions/index';
import shortid from 'shortid'
import ModalBox from '../containers/modalbox'
class RecipeBox extends Component {
constructor(props){
super(props);
this.renderRecipeList = this.renderRecipeList.bind(this)
this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this)
this.handleUserIngredientsChange = this.handleUserIngredientsChange.bind(this)
}
handleRecipeNameChange(event){
this.setState({recipeName: event.target.value})
}
handleUserIngredientsChange(event){
this.setState({userIngredients: event.target.value})
}
renderRecipeList(recipeItem, index){
const recipe = recipeItem.recipe;
const ingredients = recipeItem.ingredients;
const id = shortid.generate();
return(
<div key={id}>
<Panel bsStyle="primary" collapsible header={<h3>{recipe}</h3>}>
<ListGroup >
<ListGroupItem header="Ingredients"></ListGroupItem>
{ingredients.map(function(ingredient,index){
return <ListGroupItem key={index}>{ingredient}</ListGroupItem>;
})}
<ListGroupItem>
<Button
onClick={() => this.props.deleteRecipe(recipeItem)}
bsStyle="danger">Delete
</Button>
<ModalBox
modalTextTitle={'Edit Recipe'}
recipeName={recipe}
userIngredients={ingredients}
handleRecipeNameChange={this.handleRecipeNameChange}
handleUserIngredientsChange={this.handleUserIngredientsChange}
onClickSubmit={this.onClickSubmit}
/>
</ListGroupItem>
</ListGroup>
</Panel>
</div>
)
}
render(){
return(
<div className="container">
<div className='panel-group'>
{this.props.addRecipe.map(this.renderRecipeList)}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
addRecipe : state.recipeState
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({deleteRecipe : deleteRecipe}, dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipeBox);
src/containers/modalbox
import React, { Component } from 'react';
import { Button, Modal } from 'react-bootstrap';
class ModalBox extends Component {
constructor(props){
super(props)
this.state = {
showModal: false
};
this.toggleModal = this.toggleModal.bind(this);
}
toggleModal(){
this.setState({
showModal: !this.state.showModal
});
}
submitData(link){
link()
this.toggleModal()
}
render() {
return (
<div>
<Button
bsStyle="info"
onClick={this.toggleModal}
>
{this.props.modalTextTitle}
</Button>
<Modal show={this.state.showModal} onHide={this.toggleModal}>
<Modal.Header closeButton>
<Modal.Title>{this.props.modalTextTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label htmlFor="recipeName">Name of Recipe:</label>
<input
value={this.props.recipeName}
onChange= {this.props.handleRecipeNameChange}
type="text"
className="form-control"
id="recipeName" />
</div>
<div className="form-group">
<label htmlFor="userIngredients">Ingredients:</label>
<textarea
placeholder="you can seperate by comma"
value={this.props.userIngredients}
onChange={this.props.handleUserIngredientsChange}
type="text"
className="form-control"
id="userIngredients" />
</div>
</form>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle="info"
onClick={ () => this.submitData(this.props.onClickSubmit) }>
{this.props.modalTextTitle}
</Button>
<Button
bsStyle="danger"
onClick= {this.toggleModal}
>Close</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
export default ModalBox
inside map function you need to change the this like below code,
render(){
const self = this;
return(
<div className="container">
<div className='panel-group'>
{this.props.addRecipe.map(self.renderRecipeList)}
</div>
</div>
)
}
Related
I am fairly new to react and am having an issue. I am using reactstrap to call a modal on click of a button. In reactstrap docs the modal is called on the click of a button in the modal component like so:
import React from 'react';
import { Button, Modal, ModalFooter, ModalHeader, ModalBody } from 'reactstrap';
class SubmitConfirmation extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
fade: true,
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal,
fade: !this.state.fade,
});
}
render() {
return (
<div>
<Button color="danger" onClick={this.toggle}>
TOGGLE
</Button>
<Modal isOpen={this.state.modal} toggle={this.toggle} fade={this.state.fade} className={this.props.className}>
<ModalHeader toggle={this.toggle}>Modal title</ModalHeader>
<ModalBody></ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.toggle}>
Do Something
</Button>{' '}
<Button color="secondary" onClick={this.toggle}>
Cancel
</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default SubmitConfirmation;
I want to call the modal from the click of a button in a parent component How do I do this?
export default class SampleApp extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<div>
<Button
color="primary"
size="sm"
style={{ width: '100%' }}
onClick={this.toggle}
Submit
</Button>
</div>
)
}
}
How do I call the button from parent component:
You will need to move the state to the parent component to get this functionality,
Parent Component:
import React from "react";
import { Button } from "reactstrap";
import Child from "./Child";
export default class SampleApp extends React.Component {
constructor(props) {
super(props);
this.state = { modal: false, fade: true };
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal,
fade: !this.state.fade
});
}
render() {
return (
<>
<Button
color="primary"
size="sm"
style={{ width: "100%" }}
onClick={this.toggle}
>
Open
</Button>
<Child
modal={this.state.modal}
toggle={this.toggle}
fade={this.state.fade}
/>
</>
);
}
}
Child Component
import React from "react";
import { Button, Modal, ModalFooter, ModalHeader, ModalBody } from "reactstrap";
class SubmitConfirmation extends React.Component {
render() {
return (
<Modal
isOpen={this.props.modal}
toggle={this.props.toggle}
fade={this.props.fade}
className={this.props.className}
>
<ModalHeader toggle={this.toggle}>Modal title</ModalHeader>
<ModalBody></ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.toggle}>
Do Something
</Button>{" "}
<Button color="secondary" onClick={this.toggle}>
Cancel
</Button>
</ModalFooter>
</Modal>
);
}
}
export default SubmitConfirmation;
It seems like you forgot to add this.props.toggle
<div>
<Button
color="primary"
size="sm"
style={{ width: '100%' }}
onClick={this.props.toggle}
Submit
</Button>
</div>
Since you are passing toggle function through props, the function belongs to this.props property of your component, just like any other property you pass to child component through props
I am stumped. I have been trying to get a simple cleartext button to work. I have tried all the option available on this platform but nothing is working for me. React Hook useCallback cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook. I have no idea why this error is occurring. I am pretty new to react can anyone please help? I am trying to clear the text on a click of the clear button.
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: ""
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
const handleClick = useCallback(() => {
e.target.value = '';
}, []);
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={handleClick}> Click to clear</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
It is because you try to use hooks in a class based component. You can only use hooks like useCallback in functional components. Therefore you are mixing the concepts of object oriented and functional programming.
The following should do the trick:
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: ""
};
const handleClick = (e) => {
e.target.value = '';
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={this.handleClick}> Click to clear</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
useCallback hooks can only be used in functional components OR in a custom Hook NOT in class components.
You have declared your App component in class based approach i.e. Object oriented approach. To use useCallback hook for this, you should change your App component declaration to a functional component or Custom hook.
Something like this
function App() {
return (
<>Hello World!</>
);
}
you cannot useCallBack hook in class Component,we can do a simple thing to clear the button text, by using
a button text state, i.e. { btnTxt: "Clear ButtonText"} than update the state onclick
import React, { Component, useCallback, useState } from "react";
import {
Button,
Input,
Footer,
Card,
CardBody,
CardImage,
CardTitle,
CardText
} from "mdbreact";
import blankImg from "./blank.gif";
import "./style.css";
import "./flags.min.css";
import countriesList from "./countries.json";
class App extends Component {
state = {
search: "",
btnTxt: "Clear ButtonText"
};
renderCountry = country => {
const { search } = this.state;
var code = country.code.toLowerCase();
return (
<div className="col-md-3" style={{ marginTop: "20px" }}>
<Card>
<CardBody>
<p className="">
<img
src={blankImg}
className={"flag flag-" + code}
alt={country.name}
/>
</p>
<CardTitle title={country.name}>
{country.name.substring(0, 15)}
{country.name.length > 15 && "..."}
</CardTitle>
</CardBody>
</Card>
</div>
);
};
onchange = e => {
this.setState({ search: e.target.value });
};
render() {
const { search } = this.state;
const filteredCountries = countriesList.filter(country => {
return country.name.toLowerCase().indexOf(search.toLowerCase()) !== -1;
});
return (
<div className="flyout">
<main style={{ marginTop: "4rem" }}>
<div className="container">
<div className="row">
<div className="col">
<Input
label="Search Country"
icon="search"
onChange={this.onchange}
/>
<button onClick={()=> this.setState({btnText:""})}> {this.state.btnTExt}</button>
</div>
<div className="col" />
</div>
<div className="row">
{filteredCountries.map(country => {
return this.renderCountry(country);
})}
</div>
</div>
</main>
<Footer color="indigo">
<p className="footer-copyright mb-0">
© {new Date().getFullYear()} Copyright
</p>
</Footer>
</div>
);
}
}
export default App;
I am not able to update my comment on the page.
After submiting it is showing comment object in the console log but not on the page Console log image
here is my comment.js
import {COMMENTS} from '../shared/comments';
import * as ActionType from './ActionTypes';
export const Comments=(state=COMMENTS ,action) => {
switch(action.type){
case ActionType.ADD_COMMENT:
var comment = action.payload;
comment.id = state.length;
comment.date = new Date().toISOString();
console.log("Comment: ", comment);
return state.concat(comment);
default:
return state;
};
}
here in my store
import {createStore, combineReducers} from 'redux';
import {Dishes} from './dishes';
import {Comments} from './comments';
import {Leaders} from './leaders';
import {Promotions} from './promotions';
export const ConfigureStore=() =>{
const store=createStore(
combineReducers({
dishes:Dishes,
comments:Comments,
leaders:Leaders,
promotions:Promotions
})
);
return store;
};
here is my action creator
import * as ActionTypes from './ActionTypes';
export const addComment=(dishID,rating,author,comment) => ({
type:ActionTypes.ADD_COMMENT,
payload:{
dishID:dishID,
rating:rating,
author:author,
comment:comment,
}
});
here is my App.js
import React from 'react';
import Main from './components/MainComponent';
import './App.css';
import {BrowserRouter} from 'react-router-dom';
import {Provider} from 'react-redux';
import {ConfigureStore} from './redux/configureStore';
const store=ConfigureStore();
class App extends React.Component {
render()
{
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Main/>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
here is my MainComponent.js
import React from 'react';
import Menu from './MenuComponent';
import DishDetail from './DishdetailComponent';
import Home from './HomeComponent';
import Header from './HeaderComponent';
import Footer from './FooterComponent';
import Contact from './ContactComponent';
import About from './AboutComponent';
import {Switch , Route , Redirect,withRouter } from 'react-router-dom';
import {connect} from 'react-redux';
import {addComment} from '../redux/ActionCreators'
const mapStateToProps=state =>{
return{
dishes:state.dishes,
leaders:state.leaders,
comments:state.comments,
promotions:state.promotions
}
};
const mapDispatchToProps = dispatch => ({
addComment:(dishId,rating,author,comment) => dispatch(addComment(dishId,rating,author,comment))
});
class Main extends React.Component {
render()
{
const HomePage=() => {
return(<Home dish={this.props.dishes.filter((dish) => dish.featured)[0]}
leader={this.props.leaders.filter((leader) => leader.featured)[0]}
promotion={this.props.promotions.filter((promo) => promo.featured)[0]} />);
}
const DishWithId=({match}) => {
return(
<DishDetail dish={this.props.dishes.filter((dish) => dish.id === parseInt(match.params.dishId,10))[0]}
comments={this.props.comments.filter(comment => comment.dishId === parseInt(match.params.dishId,10))}
addComment={this.props.addComment} />
);
};
return (
<div>
<Header/>
<Switch>
<Route path="/home" component={HomePage}/>
<Route exact path="/aboutus" component={() => <About leaders={this.props.leaders}/>}/>
<Route exact path="/menu" component={() => <Menu dishes={this.props.dishes}/>} />
<Route path="/menu/:dishId" component={DishWithId} />
<Route exact path="/contactus" component={() => <Contact/>}/>
<Redirect to="/home"/>
</Switch>
<Footer/>
</div>
);
}
}
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Main));
here is my dishdetail.js component
import React from 'react';
import '../App.css';
import { Card, CardImg, CardBody, CardTitle, CardText ,Breadcrumb ,BreadcrumbItem ,Button, Modal, ModalHeader, ModalBody, Label,Row} from 'reactstrap';
import { LocalForm, Control,Errors } from 'react-redux-form';
import {Link} from 'react-router-dom';
const maxLength=(len) => (val) => !(val) || (val.length <= len);
const minLength=(len) => (val) => (val) && (val.length >= len);
class CommentForm extends React.Component
{
constructor(props)
{
super(props);
this.state={
isModalOpen:false
};
this.toggleModal=this.toggleModal.bind(this);
this.handelSubmit=this.handelSubmit.bind(this);
}
toggleModal()
{
this.setState({
isModalOpen: !this.state.isModalOpen
});
}
handelSubmit(values)
{
this.props.addComment(this.props.dishId,values.rating,values.author,values.comment);
this.toggleModal();
}
render(){
return(
<React.Fragment>
<Button outline onClick={this.toggleModal}><span className="fa fa-pencil"></span>{' '}Submit Comment</Button>
{/*--Modal For Comment--*/}
<Modal id="commentModal" isOpen={this.state.isModalOpen} toggle={this.toggleModal}>
<ModalHeader toggle={this.toggleModal}>Submit Comment</ModalHeader>
<ModalBody>
<LocalForm onSubmit={(values) => this.handelSubmit(values)} >
<Row className="form-row mt-2">
<Label htmlFor="rating">Rating</Label>
<Control.select model=".rating"
id="rating"
name="rating"
className="form-control"
>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</Control.select>
</Row>
<Row className="form-row mt-2">
<Label htmlFor="author">Your Name</Label>
<Control.text model=".author"
id="author"
name="author"
className="form-control"
placeholder="Your Name"
validators={{
minLength:minLength(3),
maxLength:maxLength(15)
}}
/>
<Errors
className="text-danger"
model=".author"
show="touched"
messages={{
minLength:'Must be greater than 2 characters',
maxLength:'Must be 15 characters or less'
}}/>
</Row>
<Row className="form-row mt-2">
<Label htmlFor="comment">Comment</Label>
<Control.textarea model=".comment"
rows="6"
id="comment"
name="comment"
className="form-control"
/>
</Row>
<Row className="form-row mt-2">
<Button type="submit" color="primary" >Submit</Button>
</Row>
</LocalForm>
</ModalBody>
</Modal>
</React.Fragment>
);
}
}
function RenderDish({dish})
{
return(
<div className="col-12 col-md-5 m-1">
<Card>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardBody>
<CardTitle><strong>{dish.name}</strong></CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
</div>
);
}
function RenderComments({comments,dishId,addComment})
{
if(comments!=null)
{
return(
<div className="col-12 col-md-5 m-1">
<h4>comments</h4>
<ul className="list-unstyled">
{comments.map(comment => {
return(
<li key={comment.id}>
<p>{comment.comment}</p>
<p>--{comment.author} ,{new Intl.DateTimeFormat('en-US',{year:'numeric',month:'short',day:'2-digit'}).format(new Date(Date.parse(comment.date)))}</p>
</li>
);
})}
</ul>
<CommentForm dishId={dishId} addComment={addComment}/>
</div>
);
}
else{
return <div></div>
}
}
function DishDetail(props)
{
if(props.dish!=null)
{
return(
<div className="container">
<div className="row">
<Breadcrumb >
<BreadcrumbItem><Link to="/home">Home</Link></BreadcrumbItem>
<BreadcrumbItem><Link to="/menu">Menu</Link></BreadcrumbItem>
<BreadcrumbItem>{props.dish.name}</BreadcrumbItem>
</Breadcrumb>
<div className="col-12">
<h3>{props.dish.name}</h3>
<hr/>
</div>
</div>
<div className="row">
<RenderDish dish={props.dish}/>
<RenderComments comments={props.comments} dishId={props.dish.id} addComment={props.addComment}/>
</div>
</div>
);
}
else
{
return(
<div></div>
);
}
}
export default DishDetail;
please help !!
I am trying to generate a random user information when pressing the button, and display the information above the button. In ProfilePanel.js, I created a avatar and user constants, which will use to show the information. In index.js, the avatar constant works for that since it doesn't need to use the Button. however, for user constant, it doesn't work. In below's code, I am fetching a api data to display user name, but it didn't show anything, I am not sure where wrong, is something wrong in Button.js or index.js. and how can I fix it. Can somebody help me out? Thanks.
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
index.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Panel from "./ProfilePanel";
import axios from 'axios';
import './index.css';
import Button from './Button';
const url = 'https://randomuser.me/api/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
contacts: []
}
}
componentDidMount() {
this.fetchdata();
}
fetchdata() {
axios.get(url)
.then(res => {
console.log(res);
this.setState({ contacts: res.data.results});
});
}
render(){
const {contacts} = this.state;
return (
<div className="panel">
{contacts.map(contact => (
<div class="panel">
<Panel
key={contact.picture} avatar={contact.picture.medium}
/>
<li class="flex-container">
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
<Button title="location" onClick={this.fetchdata}>
</Button>
<Button key={contact.email} title="email">
</Button>
<Button key={contact.phone} title="phone">
</Button>
<Button key={contact.login.password} title="password">
</Button>
</li>
</div>
))}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
ProfilePanel.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import './index.css';
import Button from './Button';
const style={
borderRadius: 150,
margin: 15,
}
class Panel extends Component {
render() {
const { avatar, user } = this.props;
return (
<div className="Panel">
<div class="panels">
<div className="avatar">
<img src={avatar} class="imageStyle" alt="" width={"200%"} height={"auto"}/>
</div>
</div>
<div class="center">
<h2 className="user">{user}</h2>
</div>
</div>
);
}
}
export default Panel;
Button.js
import './index.css';
import React, { Component } from 'react';
class Button extends Component {
constructor(props) {
super(props);
this.state = {
open:false,
};
}
render() {
const { title } = this.props;
const {open} = this.state;
return (
<button className={` ${open ? 'open' : ''}`}
class='button' onClick={(e) => this.handleClick(e)}>
<div className="panel-heading">
<h2 class='buttoncenter'>{title}</h2>
</div>
</button>
);
}
handleClick(e) {
e.preventDefault();
this.setState({
open: this.state.open
})
}
}
export default Button;
You're not changing state in the handle click. You need to set open to true;
handleClick(e) {
e.preventDefault();
this.setState({
open: true
})
}
You need to pass your user information in index.js. I think you have missed to pass the user props to the panel component, so that it shows the avatar alone. Without passing the users props, you are trying to destructure there in panel component.
//index.js should be like this
render(){
const {contacts} = this.state;
return (
<div className="panel">
{contacts.map(contact => (
<div class="panel">
<Panel
key={contact.picture} user={contact.name} avatar={contact.picture.medium}
/>
<li class="flex-container">
<Button title="name" >
<p key={contact.name} user={contact.name}></p>
</Button>
<Button title="location" onClick={this.fetchdata}>
</Button>
<Button key={contact.email} title="email">
</Button>
<Button key={contact.phone} title="phone">
</Button>
<Button key={contact.login.password} title="password">
</Button>
</li>
</div>
))}
</div>
);
}
I'm having hard time applying the concept between components vs containers using Bootstrap's modal in React-Redux.
Essentially, instead of re-creating specific modals, I'd like to create a React Component that holds a modal template.
To illustrate my example, here's a FCC project that I'd like to implement:
https://codepen.io/FreeCodeCamp/full/xVXWag/
The "Add Recipe" and "Edit" has the same component, yet when being called it is used by different containers (is this correct mindset?).
I have the following code in one of my container for "Add Recipe":
import React, { Component } from 'react';
import { Button, Modal } from 'react-bootstrap';
import { addRecipe } from '../actions/index';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class AddRecipeButton extends Component{
constructor(props){
super(props);
this.state = {recipeName: '', userIngredients: '', showModal: false};
this.close = this.close.bind(this);
this.open = this.open.bind(this);
this.onClickSubmit = this.onClickSubmit.bind(this);
this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this)
this.handleUserIngredientsChange = this.handleUserIngredientsChange.bind(this)
}
close() {
this.setState({ showModal: false, recipeName: '', userIngredients: '' });
}
open() {
this.setState({ showModal: true });
}
onClickSubmit(){
const splitIngredients = this.state.userIngredients.split(/[ ,]+/)
this.props.addRecipe([this.state.recipeName, splitIngredients])
this.setState({ showModal: false, recipeName: '', userIngredients: '' });
}
handleRecipeNameChange(event){
this.setState({recipeName: event.target.value})
}
handleUserIngredientsChange(event){
this.setState({userIngredients: event.target.value})
}
render(){
const centerText = {
textAlign : 'center'
}
return(
<div>
<Button
bsStyle="success"
onClick={this.open}
>Add Recipe
</Button>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title style={centerText}>Add Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label htmlFor="recipeName">Name of Recipe:</label>
<input
value={this.state.recipeName}
onChange={this.handleRecipeNameChange}
type="text"
className="form-control"
id="recipeName" />
</div>
<div className="form-group">
<label htmlFor="userIngredients">Ingredients:</label>
<textarea
placeholder="you can seperate by comma"
onChange = {this.handleUserIngredientsChange}
value={this.state.userIngredients}
type="text"
className="form-control"
id="userIngredients" />
</div>
</form>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle="info"
onClick={this.onClickSubmit}>Add Recipe
</Button> <Button
bsStyle="danger"
onClick={this.close}>Close
</Button>
</Modal.Footer>
</Modal>
</div>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addRecipe}, dispatch)
}
export default connect(null,mapDispatchToProps)(AddRecipeButton)
Although this works, I can already tell that the render function should call a component that does the actual rendering of the modal instead.
I guess my question is how to create the modal component and keep track of the modal window state?
EDIT:
Those who are curious, I was able to implement what it worked for me.
Modal Component:
import React, { Component } from 'react'
import { Button, Modal } from 'react-bootstrap';
export default (props) => {
return (
<Modal show={props.showModal} onHide={props.toggleModal}>
<Modal.Header closeButton>
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label htmlFor="recipeName">Name of Recipe:</label>
<input
value={props.recipeName}
onChange={props.handleRecipeNameChange}
type="text"
className="form-control"
id="recipeName" />
</div>
<div className="form-group">
<label htmlFor="userIngredients">Ingredients:</label>
<textarea
placeholder="you can seperate by comma"
onChange = {props.handleUserIngredientsChange}
value={props.userIngredients}
type="text"
className="form-control"
id="userIngredients" />
</div>
</form>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle="info"
onClick={props.onClickSubmit}>Add Recipe
</Button> <Button
bsStyle="danger"
onClick={props.toggleModal}>Close
</Button>
</Modal.Footer>
</Modal>
)
}
Add Recipe Button Container (smart component)
import React, { Component } from 'react';
import { Button, Modal } from 'react-bootstrap';
import { addRecipe } from '../actions/index';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import MyModal from '../components/mymodal';
class AddRecipeButton extends Component{
constructor(props){
super(props);
this.state = {
recipeName: '',
userIngredients: '',
showModal: false
};
this.onClickSubmit = this.onClickSubmit.bind(this);
this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this)
this.handleUserIngredientsChange = this.handleUserIngredientsChange.bind(this)
this.toggleModal = this.toggleModal.bind(this);
}
toggleModal(){
this.setState({
showModal: !this.state.showModal
});
}
onClickSubmit(){
const splitIngredients = this.state.userIngredients.split(/[ ,]+/)
this.props.addRecipe([this.state.recipeName, splitIngredients])
this.toggleModal()
}
handleRecipeNameChange(event){
this.setState({recipeName: event.target.value})
}
handleUserIngredientsChange(event){
this.setState({userIngredients: event.target.value})
}
render(){
return (
<div>
<Button
bsStyle="success"
onClick={this.toggleModal}
>Add Recipe
</Button>
<MyModal
toggleModal={this.toggleModal}
showModal={this.state.showModal}
recipeName={this.state.recipeName}
userIngredients={this.state.userIngredients}
handleRecipeNameChange={this.handleRecipeNameChange}
handleUserIngredientsChange={this.handleUserIngredientsChange}
onClickSubmit={this.onClickSubmit}
/>
</div>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addRecipe}, dispatch)
}
export default connect(null,mapDispatchToProps)(AddRecipeButton)
Something like this would work (kind of a quick job but you get the idea..?)
// Create a React component for your modal:
var MyModal = React.createClass({
render: function() {
return (
<Modal onHide={this.props.handleToggle}>
<Modal.Header closeButton>
<Modal.Title style={centerText}>Add Recipe</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label htmlFor="recipeName">Name of Recipe:</label>
<input
value={this.props.recipeName}
onChange={this.props.handleRecipeNameChange}
type="text"
className="form-control"
id="recipeName" />
</div>
<div className="form-group">
<label htmlFor="userIngredients">Ingredients:</label>
<textarea
placeholder="you can seperate by comma"
onChange = {this.props.handleUserIngredientsChange}
value={this.props.userIngredients}
type="text"
className="form-control"
id="userIngredients" />
</div>
</form>
</Modal.Body>
<Modal.Footer>
<Button
bsStyle="info"
onClick={this.props.onClickSubmit}>Add Recipe
</Button> <Button
bsStyle="danger"
onClick={this.props.handleToggle}>Close
</Button>
</Modal.Footer>
)
}
});
// Return modal in render function and pass parent components state and functions down through props:
var AddRecipeButton = React.createClass({
getInitialState: function() {
return {
isModalOpen: false,
recipeName: '',
userIngredients: ''
};
},
toggleModal: function() {
this.setState({
isModalOpen: !this.state.isModalOpen
});
},
onClickSubmit(){
const splitIngredients = this.state.userIngredients.split(/[ ,]+/)
this.props.addRecipe([this.state.recipeName, splitIngredients])
this.toggleModal();
},
handleRecipeNameChange(event){
this.setState({recipeName: event.target.value})
},
handleUserIngredientsChange(event){
this.setState({userIngredients: event.target.value})
},
renderModal: function() {
if (this.state.isModalOpen) {
return
<MyModal
toggleModal={this.toggleModal}
onClickSubmit={this.onClickSubmit}
handleRecipeNameChange={this.handleRecipeNameChange}
handleUserIngredientsChange={this.handleUserIngredientsChange}
/>;
}
},
render: function() {
{this.renderModal()}
}
});