Deployed React app not working same as local version - reactjs

I have a redux action which sends a request to update some data in the backend and on the response I dispatch another action to set this updated data. Heres the action:
export const openLecture = lectureId => dispatch => {
axios
.put(`/api/lectures/open/${lectureId}`)
.then(res => {
if (res.data.lecture) {
console.log(res.data.lecture);
dispatch(getLecture(res.data.lecture._id));
}
})
.catch(err => {
console.log(err);
});
};
Theres a component which takes this data from the redux state adn passes it to a child component. Heres the child component:
class LectureHeader extends Component {
constructor() {
super();
this.state = {};
this.makeLectureLive = this.makeLectureLive.bind(this);
this.closeLecture = this.closeLecture.bind(this);
}
componentWillReceiveProps(newProps) {
this.setState({ ...newProps });
}
makeLectureLive() {
this.props.openLecture(this.props.id);
}
closeLecture() {
this.props.closeLecture(this.props.id);
}
render() {
const { form, status, notes, id, date, code, name } = this.state;
let formBtn, liveBtn, liveStatus, progress, liveColor;
let formLink = `/dashboard/form/${id}`;
if (typeof form !== "undefined") {
if (form.length > 0) {
formBtn = (
<a id="formBtn" href={formLink} className="btn btn-dark">
Edit form
</a>
);
} else {
formBtn = (
<a id="formBtn" href={formLink} className="btn btn-dark">
Create form
</a>
);
}
//Lecture has not been live yet
if (status.iat === null && status.exp === null) {
liveBtn = (
<a
id="liveBtn"
href="javascript:void(0)"
className="btn btn-primary"
data-toggle="modal"
data-target=".bd-example-modal-sm"
>
Make Live
</a>
);
} else if (status.iat <= Date.now() && status.exp >= Date.now()) {
//Lecture is currently live
liveBtn = (
<a
id="liveBtn"
onClick={this.closeLecture}
href="javascript:void(0)"
className="btn btn-primary"
>
Close Lecture
</a>
);
formBtn = (
<a id="formBtn" href={formLink} className="btn btn-dark">
see form
</a>
);
liveColor = "#05c435";
let percent =
(100 * (Date.now() - status.iat)) / (status.exp - status.iat);
let percentWidth = percent + "%";
progress = (
<div style={{ height: "8px" }} className="progress">
<div
className="progress-bar"
role="progressbar"
style={{ width: percentWidth }}
aria-valuenow={percent}
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
);
} else {
//Lecture was live and now is closed
formBtn = (
<a id="formBtn" href={formLink} className="btn btn-dark">
see form
</a>
);
}
}
let confirmModal = (
<div
className="modal fade bd-example-modal-sm"
tabIndex="-1"
role="dialog"
aria-labelledby="mySmallModalLabel"
aria-hidden="true"
>
<div className="modal-dialog modal-sm">
<div style={{ padding: "20px" }} className="modal-content">
<p>Are you sure you want to make this lecture live?</p>
<a
id="liveBtn"
href="javascript:void(0)"
className="btn btn-primary"
data-dismiss="modal"
aria-label="Close"
onClick={this.makeLectureLive}
>
Yes
</a>
<a
id="confirmCancel"
href={formLink}
className="btn btn-dark"
data-dismiss="modal"
aria-label="Close"
>
Cancel
</a>
</div>
</div>
</div>
);
return (
<div className="lecture-header">
<div>
<h1 style={{ color: liveColor }}>{name}</h1>
{formBtn}
{liveBtn}
{confirmModal}
<hr />
<h6 id="lecture-code">code: {code}</h6>
<small className="text text-muted">{date}</small>
<h6>{notes}</h6>
</div>
{progress}
</div>
);
}
}
LectureHeader.propTypes = {
openLecture: PropTypes.func.isRequired,
closeLecture: PropTypes.func.isRequired,
name: PropTypes.string,
notes: PropTypes.string,
form: PropTypes.array,
status: PropTypes.object,
id: PropTypes.string
};
export default connect(
null,
{ openLecture, closeLecture }
)(LectureHeader);
The problem Im having is that this component si fully updating when I run the app locally. However I have it deployed in two places and neither of them fully update the component. The component changes slightly but doesnt actually update based on any of the new data. I cant figure out why any suggestions would be great. Thanks in advance.

Related

React Element appearing after second button click

Im fairly new to react and im trying to build a webapp. I'm having this problem when it comes to displaying a div that contains multiple input fields, the field lesson dont show until the second time I click my create button. However it's working perfectly when I render it with this code:
Note: Im rendering TestClonereact in CourseVideoStructure
This is CourseVideoStructure.js
import React, { Component } from 'react'
import Sidenavbar from '../../Components/Sidenavbar'
import '../../Style/pagestyle/CourseVideoStructure.css'
import Footer from '../../Components/Footer'
import { Avatar } from '#material-ui/core'
import 'react-dropzone-uploader/dist/styles.css'
import EdiText from 'react-editext'
import TestClonereact from '../../Components/TestClonereact'
export default class CourseVideoStructure extends Component {
onSave = val => {
console.log('Edited Value -> ', val)
}
id = 1;
state = {
listq: [],
}
lessons(id) {
return (
<div key={id} id={`sectionlesson-${id}`}>
<div className="section-titles">
<i class="material-icons" id="iconsectionlist" type="button" >list</i>
<EdiText
type='text'
value='Lesson Title'
onSave={this.onSave}
/>
<i class="material-icons" id="iconsectiondel" type="button">smart_display</i>
<i class="material-icons" id="iconsectiondel" onClick={e => this.remove(`${id}`)} type="button" >delete</i>
</div>
<div className="testh" >
</div>
</div>
)
}
addlesson1 = () => {
const listlesson = this.state.listq;
listlesson.push(this.lessons(this.id))
this.setState({ listlesson });
this.id++;
}
remove(id) {
const newList = this.state.listq.filter(lessons => lessons.key !== id);
this.setState({
listq: newList
})
}
addsection = () => {
const list = this.state.list;
list.push(this.section(this.id))
this.setState({ list });
this.id++;
}
render() {
return (
<div>
<Sidenavbar></Sidenavbar>
<nav class="navbar navbar-expand-md navbar-white">
<a class="navbar-brand" href="#"></a>
<Avatar className="avatar" aria-controls="simple-menu" aria-haspopup="true" src="https://avatars2.githubusercontent.com/u/56764954?s=400&u=57d85794b8c753245870e2f9051b303d1188b01d&v=4" ></Avatar>
</nav>
<div className="row">
<div className="fluid-xld" id="coursevideo-structure-xld">
<div className="course-structure-form-dsp col-md-12">
<h3 className="container"> Video Course Structure</h3><br></br><br></br>
<div className="course-structure-form " id="csf1">
<div className="section-heading">
<i class="material-icons" id="iconsection">api</i>
<EdiText
type='text'
value='Section Title'
onSave={this.onSave}
/>
</div>
{
this.state.listq.map(lessons => lessons)
}
<div className="addnewlesson" onClick={e => this.addlesson1()}>
<i class="material-icons" id="iconsectionde" role="button" type="button" >add_circle</i><span>Add New Lesson</span>
</div>
</div>
</div>
<div>
<TestClonereact></TestClonereact>
</div>
</div>
</div>
<Footer></Footer>
</div>
)
}
}
However when I try to render this component in CourseVideoStructure(the code above) it doesnt render as I would like it to:
This is TestClonereact.jsx:
import React, {useEffect, useReducer} from 'react'
import '../Style/pagestyle/CourseVideoStructure.css'
import EdiText from 'react-editext'
class TestClonereact extends React.Component {
constructor(props) {
super(props);
this.state = {
listq: [],
list: []
};
}
onSave = val => {
console.log('Edited Value -> ', val)
}
id = 1;
lessons(id){
return (
<div key={id} id={`sectionlesson-${id}`}>
<div className="section-titles">
<i class="material-icons" id="iconsectionlist" type="button" >list</i>
<EdiText
type='text'
value='Lesson Title'
onSave={this.onSave}
/>
<i class="material-icons" id="iconsectiondel" type="button">smart_display</i>
<i class="material-icons" id="iconsectiondel" onClick={e => this.remove(`${id}`)} type="button" >delete</i>
</div>
<div className="testh" >
</div>
</div>
)
}
section(id){
return (
<div key={id} id={`sds-${id}`}>
<div className="course-structure-form " id="csf1">
<div className="section-heading">
<i class="material-icons" id="iconsection">api</i>
<EdiText
type='text'
value='Section Title'
onSave={this.onSave}
/>
</div>
{
this.state.listq.map(lessons => lessons)
}
<div className="addnewlesson" onClick={async () => { this.addlesson();}}>
<i class="material-icons" id="iconsectionde" role="button" type="button" >add_circle</i><span>Add New Lesson</span>
</div>
</div>
</div>
)
}
addsection = () => {
const list = this.state.list;
list.push(this.section(this.id))
this.setState({ list });
this.id++;
}
addlesson = () => {
const listlesson = this.state.listq;
listlesson.push(this.lessons(this.id))
this.setState({ listlesson });
this.id++;
}
deletelesson = () => {
const list = this.state.list;
list.splice(this.section(this.id))
this.setState({ list });
this.id--;
}
remove(id) {
const newList = this.state.listq.filter(lessons => lessons.key !== id);
this.setState({
listq: newList
})
}
render(){
return (
<div>
{
this.state.list.map(section => section)
}
<div className="add-section-button-structure">
<button class="tablink" onClick={e => this.addsection()} >Add New Section</button>
<button class="tablink" onClick={e => this.deletelesson()} >Delete Section</button>
<button class="tablink" >Preview</button>
<button class="tablink" >Submit</button>
</div>
</div>)
}
}
export default TestClonereact
So the result in images is this:
Working fine and Added Lessons 1 by 1 in the same Section:
Working Fine and able to add lessons
Problem is when I click on Add Lesson in the Second section, nothing happens but when I click on Add section again, the lesson appear in the 3rd section not the second one:
Problem image here
I've kept tryint to solve this issue with no luck. Any kind of help is much appreciated.
Forgive me for any horrible coding.

React - Using a Callback function to pass data from Child to Parent

I am currently making a simple Dropdown menu in which I want to pass certain state values to its parent so that the parent will fetch data from the backend.
I have written a callback function in my parent function that I am passing as a prop to the child; however, I am not getting any output when I log the change on the console, hence I don't think I am using the callback properly.
This is my dropdown menu:
class Dropdown extends Component {
constructor(props) {
super(props);
this.state = {
headerTitle: this.props.title,
teams: this.props.teams,
};
this.handleClick = this.handleClick.bind(this)
}
handleClick(teamID) {
this.setState({
headerTitle: this.state.teams[teamID].name
})
}
render() {
return (
<div className="btn-group w-100 d-flex">
<button type="button" className="btn-success w-100 dropdown-toggle" data-toggle="dropdown" onChange={this.props.handleTeam}>
{this.state.headerTitle}
</button>
<ul className="dropdown-menu pre-scrollable w-100" role="menu">
{this.state.teams.map((team) => {
return (
<li>
<a href="#/game-pred" className="dropdown-item d-flex justify-content-center" onClick={() => this.handleClick(team.id)} >{team.name}</a>
</li>
);
})}
</ul>
</div>
);
}
}
And this is my parent:
class GamePrediction extends Component {
constructor(props){
super(props);
this.state = {
teams: [],
count: 0,
};
this.handleTitleChange = this.handleTitleChange.bind(this);
}
handleTitleChange(newTitle) {
if (this.state.count == 2){
this.setState({
teams: [newTitle,this.state.teams[1]]
})
}
else if (this.state.count == 1) {
this.setState({
teams: [this.state.teams[0],newTitle],
count: 2
})
}
else {
this.setState({
teams: teams.push(newTitle),
count: this.state.count + 1
})
}
console.log("Change!",this.state.teams,this.state.count)
}
render() {
return (
<div className="button-group w-100 d-flex" >
<Dropdown title="Team 1" teams={teams} handleTeam={this.handleTitleChange} ref="team1"/>
<Dropdown title="Team 2" teams={teams} ref="team2"/>
</div>
);
}
}
It should also be noted that there are two dropdown menus from which the user can select one team for each dropdown, if that helps.
A <button> has an onClick, not an onChange,
so you probably want to change this
<button type="button" className="btn-success w-100 dropdown-toggle" data-toggle="dropdown" onChange={this.props.handleTeam}>
to this
<button type="button" className="btn-success w-100 dropdown-toggle" data-toggle="dropdown" onClick={this.props.handleTeam}>
Also I see that your handleTitleChange(newTitle) accepts a newTitle param, you can pass that param like this
<button type="button" className="btn-success w-100 dropdown-toggle" data-toggle="dropdown" onClick={() => this.props.handleTeam('My new Title')}>

Reactjs use function from parent

I have 2 files. One is my app.js and the other one is productmodal.js
app.js gets a productlist from an api call. It views these productst in a list.
When a user clicks on the image productmodal.js is showed.
productmodal.js shows a popup with a bigger image, productname(title) and a button called "Product niet aanwezig" (product unavailable) if the user clicks on this link a mail is send to an other user.
When the button is click I also want to activate an other function (getUpdatedProduct). This function is gonna do a long polling call to a link to check when the product is updated to a new product and update this product in the app.
The problem is: I don't know how to call the function 'getUpdatedProduct' in productmodal.js
I get an error: Uncaught TypeError: Cannot read property 'getUpdatedProduct' of undefined
I tried some of these solutions https://reactjs.org/docs/faq-functions.html. Especially the arrow function in render, because its generating a new function every time when the modal is clicked (which I need).
But nothing seems to work. Has anyone some idea's?
App.js:
import React from 'react';
import image from '../images/sogyologo.svg';
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: [],
productDetail: [],
open: false,
modal: []
}
}
toggleModal(event)
{
console.log(event);
let itemIndex = event.target.getAttribute("data-itemIndex");
console.log(itemIndex);
const productModal = this.state.orders[itemIndex];
console.log(productModal);
this.setState({
open: true,
modal: this.state.orders[itemIndex]
});
}
handleClose() {
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/testing-9!8-7!6/10-04-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))
}
// componentWillUpdate(nextProps, nextState) {
// localStorage.setItem('orders', JSON.stringify(nextState.orders));
// localStorage.setItem('ordersDate', Date.now());
// }
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} = this.state;
return (
<div>
<header>
<img src={image}/>
<h1>Boodschappenlijstje <button className="btn btn-sm btn-danger">Reload</button></h1>
</header>
<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.bind(this) }>
<img className="img-responsive" data-itemIndex={index} 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} = 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>
<ProductModal open={this.state.open} handleClose={this.handleClose.bind(this)}
title={this.state.modal.productname} plaatje={this.state.modal.image} orderid={this.state.modal.orderid}/>
<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();
this.getUpdatedProduct = this.getUpdatedProduct.bind(this);
}
handleClose() {
this.props.handleClose();
}
UserAction(event) {
let orderid = event.target.value;
fetch('http://localhost:54408/api/orders/change/testing-9!8-7!6/' + orderid + '/10-04-2018');
console.log("order id = " + event.target.value);
this.getUpdatedProduct();
}
getUpdatedProduct() {
console.log("fetching new product");
}
render() {
//const open = this.props.open;
const {title, plaatje, open, orderid} = this.props;
return (
<div className={'modal fade in '+(open?'show':'')} role="dialog">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" onClick={this.handleClose.bind(this)} className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 className="modal-title">{title}</h4>
</div>
<div className="modal-body">
<img className="plaatjediv img-responsive" src={plaatje} />
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default"
onClick={this.handleClose.bind(this)}>Sluiten
</button>
<button type="button" onClick={this.UserAction.bind()} value={orderid} className="btn btn-primary">Product niet aanwezig</button>
</div>
</div>
</div>
</div>
)
}
}
export default ProductModal;

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.

How to explain strange behavior of React component with UUID as a key?

I have some React component generating a list. The list contains data fetched from DB , but user can also add new element to the list.
const mapStateToProps = (state, ownProps) => ({
reservations: Object.values(state.reservationsMap).filter(r => r.weekdayId === ownProps.weekdayId),
projectsMap: state.projectsMap
})
class ReservationListBox extends Component {
render() {
let reservations = this.props.reservations.map((reservation) => {
let project = this.props.projectsMap[reservation.projectId];
let hoursInput;
return (
<div key={reservation.id}>
<div className="btn-group timesheet-project-column">
<button type="button"
className="btn btn-primary">{project.projectName}</button>
<button type="button" className="btn btn-primary dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span className="caret"></span>
<span className="sr-only">Toggle Dropdown</span>
</button>
<AvailableProjectsListBox reservationId={reservation.id}/>
</div>
<div className="timesheet-column">
<button type="button" className="btn btn-default">
<span className="glyphicon glyphicon-pencil"/>
</button>
</div>
<div className="timesheet-column">
<input ref={ node => {hoursInput = node}} type="text" className="form-control timesheet-hour"
value={reservation.hours}
onChange={()=> this.props.dispatch(fillHours(reservation.id, hoursInput.value))}/> // when I change value here it works but elements are displayed in different order
</div>
<div className="timesheet-column">
<button type="button" className="btn btn-danger"
onClick={() => this.props.dispatch(removeReservation(reservation.id))}>
<span className="glyphicon glyphicon-trash img-circle text-danger"></span>
</button>
</div>
</div>
);
});
return (
<div className="col-lg-5">
{reservations}
</div>
);
}
}
ReservationListBox = connect(mapStateToProps)(ReservationListBox);
export default ReservationListBox
---- Component where I add element to List is top level component of ReservationListBox (code below)
const mapStateToProps = (state, ownProps) => ({
weekday: state.weekdaysMap[ownProps.weekdayId]
})
class TimesheetTableRow extends Component {
render() {
return (
<div className="row timesheet-row">
<div className="col-lg-1">
<span>{this.props.weekday.day}/{this.props.weekday.month}/{this.props.weekday.year}</span>
</div>
<ReservationListBox weekdayId={this.props.weekday.id}/>
<div className="col-lg-1">
<button type="button" className="btn btn-success"
onClick={()=> this.props.onAddReservation(this.props.weekdayId)}>
<span className="glyphicon glyphicon-plus img-circle text-success"></span>
</button>
</div>
<div className="col-lg-2">
<button type="button" className="btn btn-default">
<span className="glyphicon glyphicon-pencil"/>
</button>
</div>
<div className="col-lg-1">
<button type="button" className="btn btn-success" title="Set default">
<span className="glyphicon glyphicon-time img-circle text-success"/>
</button>
</div>
<div className="col-lg-2">
<span>{this.props.weekday.statusCode}</span>
</div>
</div>
);
}
}
TimesheetTableRow = connect(
mapStateToProps,
{
onAddReservation: addReservation
}
)
(TimesheetTableRow);
export default TimesheetTableRow
as a key (for div) I use long value for reservations fetch from DB and generated UUID for new ones.
Problem:
eg. If I have one element in the list fetched and add two (o more) new ones, and next I provide value for the second (middle) element input field (invoking onChange), React replaces this element with the last one, making the list reordered. The problem does't take place when I generate id as number
// actions where I generate id for new element
import { v4 } from "node-uuid";
export const addReservation = (weekdayId) => ({
type: 'ADD_RESERVATION',
weekdayId,
reservationId: v4()
})
but this below works correctly , list isn't reordered
var nextId = 100;
export const addReservation = (weekdayId) => ({
type: 'ADD_RESERVATION',
weekdayId,
reservationId: nextId++
})
Here is a reducer handling ADD_RESEVATION action.
const reservationsMap = (state = {}, action = {}) => {
switch (action.type) {
case 'SELECT_PROJECT':
if (!_.hasIn(state, action.reservationId)) {
return state;
}
return createNewReservationState(state, action.reservationId, "projectId", action.selectedProjectId);
case 'FILL_HOURS':
if (!_.hasIn(state, action.reservationId)) {
return state;
}
return createNewReservationState(state, action.reservationId, "hours", action.hours);
case 'ADD_RESERVATION':
return {
...state,
[action.reservationId]: {
id: action.reservationId,
weekdayId: action.weekday.id,
hours: action.weekday.defaultHoursNumber,
projectId: action.weekday.defaultProjectId
}
};
case 'REMOVE_RESERVATION':
if (!_.hasIn(state, action.reservationId)) {
return state;
}
return _.assign({}, _.omit(state, action.reservationId));
case 'SET_DEFAULT_PROJECT':
return state;
default:
return state;
}
}
const createNewReservationState = (state, id, property, value) => {
let reservation = _.set(_.assign({}, _.pick(state, id)[id]), property, value);
let reservationEntry = _.set(_.assign({}, _.pick(state, id)), id, reservation);
return _.assign({}, _.omit(state, id), reservationEntry);
}
SOLUTION:
replacing createNewReservationState and return result:
_.mapValues(state, (value) => {
if (value.id == action.reservationId) {
return {
id: value.id,
weekdayId: value.weekdayId,
hours: action.hours,
projectId: value.projectId
}
}
return value
}

Resources