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

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

Related

Deployed React app not working same as local version

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.

how to hide a bootstrap modal using React JSX

I am learning react and I want to close a bootstrap modal after a 'Post' callback
So, in the below code I want to hide the modal in the 'hideModal' method when onClick={() => postDocument.callApi(this.state.document, this.hideModal)} calls back.
How can I do this?
import React, { Component } from "react";
import postDocument from "./../rest/PostDocument";
import fetchPersons from "./../rest/FetchPersons";
import PersonList from "./../components/PersonList";
import ShowDatePicker from "./../components/ShowDatePicker";
import moment from "moment";
class SaveDocument extends Component {
state = {
persons: [],
document: {
documentDate: moment(),
personFrom: {
id: ""
},
personTo: {
id: ""
},
comments: ""
}
};
hideModal = hideModalInfo => {
// How do I hide the modal here!
};
render() {
return (
<div
className="modal fade"
id="basicExampleModal"
tabIndex="-1"
role="dialog"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLabel">
Save document
</h5>
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-primary"
onClick={() => postDocument.callApi(this.state.document, this.hideModal)}
>
Save changes
</button>
</div>
</div>
</div>
</div>
);
}
}
export default SaveDocument;
You can import jquery and call Bootstrap's modal("hide") method...
import $ from "jquery";
...
hideModal = hideModalInfo => {
$("#myModal").modal("hide");
};
https://codesandbox.io/s/ykkvl7547j
A different approach is to simulate a .click() on the close button,
...
const closeRef = useRef();
...
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
ref={useRef}
>
...
<button
type="button"
className="btn btn-primary"
onClick={() => {closeRef.current.click()}}
>
Save changes
</button>
you should bind that method. onClick={this.handleClick}
class SaveDocument extends Component {
constructor(props){
super(props);
state = { ... }
//This binding is necessary to make `this` work in the callback
this.hideModal = this.hideModal.bind(this);
}
render(){
return (<button
type="button"
onClick={this.hideModal)}
>)
}
};
more info about bindings
Also, i have a basic example working:
https://codepen.io/ene_salinas/pen/yRGMpG
ReactModal.setAppElement('#main');
class ExampleApp extends React.Component {
constructor () {
super();
this.state = {
showModal: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
}
handleOpenModal () {
this.setState({ showModal: true });
}
handleCloseModal () {
this.setState({ showModal: false });
}
render () {
return (
<div>
<button onClick={this.handleOpenModal}>Trigger Modal</button>
<ReactModal
isOpen={this.state.showModal}
contentLabel="onRequestClose Example"
onRequestClose={this.handleCloseModal}
className="Modal"
overlayClassName="Overlay"
>
<p>Modal text!</p>
<button onClick={this.handleCloseModal}>Close Modal</button>
</ReactModal>
</div>
);
}
}
const props = {};
ReactDOM.render(<ExampleApp {...props} />, document.getElementById('main'))
In your code (if you have jquery):
constructor(){
this.state = { hideModal = true }
}
hideModal() {
this.state.hideModal = true
//i dont recommend, i prefer use the state
$("#modal").hide()
};
showModal() {
this.state.hideModal = false
//i dont recommend, i prefer use the state
$("#modal").show()
};
render(){
<button value="open modal" onClick={showModal} />
//div modal
<div class={this.state.hideModal ? 'hide' : 'show'}>
<button value="close modal" onClick={hideModal} />
</div>
}
bootstarp has jquery based functions so
1.if you use jquery here use this one
$("#basicExampleModal").modal("hide");
2.Or we can use data-dismiss="modal" to that button
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>

error while using state and map in reactjs

I am new to react. I am fetching github user info on search. I unable
to fetch data in my child component. this is my code below.
whats the problem , cant i use this.state.userList.map
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
this.toggle = this.toggle.bind(this);
this.getUsers = this.getUsers.bind(this);
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
// componentWillMount(){
// this.getUsers();
// }
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList})
console.log(userList);
}
);
}
render() {
return (
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-primary navbar-inner">
<div className="collapse navbar-collapse navbar-inner navb" >
<ul className="navbar-nav bg-light mr-auto">
<li className="nav-item dropdown">
<a className="nav-link dropdown-toggle auto" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sort
</a>
<div className="dropdown-menu" aria-labelledby="navbarDropdown">
<a className="dropdown-item" href="#">Sort by Name (ascending)</a>
<a className="dropdown-item" href="#">Sort by Name (descending)</a>
<div className="dropdown-divider"></div>
<a className="dropdown-item" href="#">Sort by Rank (ascending)</a>
<a className="dropdown-item" href="#">Sort by Rank (descending)</a>
</div>
</li>
</ul>
<form className="form-inline my-2 my-lg-0 auto" onSubmit={this.getUsers}>
<div className="form-group">
<input className="form-control mr-sm-2" type="Search" placeholder="Search"
aria-label="Search"
id="userName"
onKeyUp={this.getUsers} >
</input>
</div>
</form>
</div>
</nav>
<div >
<UserList userList={this.state.userList}/>
</div>
</div>
);
}
}
export default SearchHeader;
This is my child component below where I am fetching data from parent
component
This is my child component below where I am fetching data from parent
component
class UserList extends Component {
constructor(props) {
super(props)
this.state ={
users:this.props.userList
}
}
render() {
return (
<div className="container-fluid">
<br />
{
this.state.users.map((user)=>
<div className="jumbotron container">
{user.login}
</div>
)
}
</div>
);
}
}
export default UserList;
You have several problems in your components:
do not copy parent's state into chilren states: users:this.props.userList. Use this.props directly instead and React will know it must re-render children
do not rely on current state to set new state. Use function with prevState instead of isOpen: !this.state.isOpen.
make a copy of event's value before passing it to setState like this const {value} = e.target;
assign unique key to each user in your list (not indexes!), or it won't re-render correctly on list update
So your code would look like this:
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
userList: [],
isOpen: false,
userName:''
};
}
toggle = () => {
this.setState( (prevState) => ({
isOpen: !prevState.isOpen
}));
}
getUsers = (e) => {
const {value} = e.target;
console.log('get users called='+value);
fetch('https://api.github.com/search/users?q='+ value)
...
}
}
and:
class UserList extends Component {
// Use default constructor
render() {
const users = this.props.userList.map( (user) => (
<div className="jumbotron container" key={user.login}>
{user.login}
</div>
));
return (
<div className="container-fluid">
<br />
{users}
</div>
);
}
}
parent component change should be.
getUsers(e) {
console.log('get users called='+e.target.value);
fetch('https://api.github.com/search/users?q='+ e.target.value)
.then(res => res.json())
.then(
userList =>{
this.setState({userList: userList.items})
console.log(userList);
}
);
}
Change your user list and check initially values are there or not and you dont need to user state in userList component.
that is all because initially there are no values also there can be an case when you are setting state for userList value after fetching data that might be coming as null undefined or something else so put an console log there and check that too.
class UserList extends Component {
render() {
return (
{
this.props.userList && this.props.userList.length && this.props.userList.map((user)=>
{user.login}
)
}
</div>
);
}
}
export default UserList;

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.

Reactjs multiple form submit

In my case, i would like to submit multiple forms in reactjs. But i have no idea on how to get the multiple form at Parent Component and submit.
here is my code:
class BulkEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
customCompanies: []
};
this.forms = [];
this.onAddChild = this.onAddChild.bind(this);
this.handleBulkSaveClick = this.handleBulkSaveClick.bind(this);
}
handleBulkSaveClick(event) {
event.preventDefault();
}
/*
* -- Add Children
*/
onAddChild() {
this.state.items.push(BulkEditorForm.defaultProps);
this.setState({
items: this.state.items
});
}
render() {
var forms = this.state.items.map(function(item, index) {
return (
<li className="list-group-item" key={index}>
<BulkEditorForm companies={this.state.customCompanies} item={item}
ref="editorform"></BulkEditorForm>
</li>
);
}.bind(this));
return (
<ul className="list-group">
{forms}
<li className="list-group-item">
<div className="btn-group btn-group-sm pull-right" role="group" aria-label="bulk-buttons">
<a href="javascript:;" className="btn btn-primary" onClick={this.onAddChild.bind(this)}>
<span className="glyphicon glyphicon-plus"></span>
</a>
<a href="javascript:;" className="btn btn-default" onClick={this.handleBulkSaveClick}>Bulk Save</a>
</div>
<div className="clearfix"></div>
</li>
</ul>
);
}
}
Here is next class
export default class BulkEditorForm extends React.Component {
constructor(props) {
super(props);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleFormSubmit(event) {
event.preventDefault();
alert("Submit");
}
render() {
return (
<form action='#' method="post" onSubmit={this.handleFormSubmit}>
<button type="submit" className="btn btn-link">Save</button>
</form>
);
}
}
In your loop of rendering form list, use different ref value for each form:
<BulkEditorForm companies={this.state.customCompanies} item={item}
ref={"editorform"+index}></BulkEditorForm>
Then after all forms are rendered, access the form list by refs in your Parent Component, which means adding componentDidMount() function as follows:
class BulkEditor extends React.Component {
constructor(props) {
}
componentDidMount() {
//using basic javascript "FOR" loop ^^
for (i = 0; i < this.state.items.length; i++) {
this.forms.push(this.refs["editorform"+index]);
}
}
}
I didn't have time for testing all the code, but that's the idea! If it doesn't work yet, feel free to post here some error logs, then we may solve it together, thanks ^^

Resources