I have a div with a className of results and I want to make so that when I click on the Check button the div gets populated with the info from my store: Shop1, Shop2. I added an observable field on my CardCheck component that you toggle with the onClick event handler, and when the field is true it should display all entries in the auto array.
Here is my Component:
import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import PropTypes from 'prop-types';
import './cardCheck.css';
#inject('auto')
#observer
class CardCheck extends Component {
onClick = event => {
event.preventDefault();
this.checked = !this.checked;
};
render() {
const { auto } = this.props;
return (
<div>
<div className="newsletter-container">
<h1>Enter the ID of your card:</h1>
<div className="center">
<input type="number" />
<input type="submit" value="Check" onClick={this.onClick} />
</div>
<div className="results">{this.checked && auto.auto.map(a => <div key={a.name} />)}</div>
</div>
<h1>Offers:</h1>
</div>
);
}
}
CardCheck.propTypes = {
auto: PropTypes.shape({
carCount: PropTypes.string
})
};
export default CardCheck;
and here is my store:
import { observable, action, computed } from 'mobx';
class Auto {
#observable
auto = [
{
name: 'Shop1'
},
{
name: 'Shop2'
}
];
#observable checked = false;
#action showInfo
}
}
export { Auto };
Right now nothing happens when I click on the Check button, why, and how can I make it finally work?
I tried iterating through the object with a loadash method but it did not populated the div, and I also tried this in the div results:
{this.checked &&
auto.auto.map((a, index) => <div key={index}>{a.name}</div>)}
but it didn't work and it gave me this error: Do not use Array index in keys react/no-array-index-key
I think you have mistyped with checked field. It should be this.auto.checked
Related
I have a ticket as you can see in the picture below:
I have a delete button as a component and I am trying to add delete functionality to it. I am using this component in my ticket component. So this is my delete component:
<template>
<div id="delete-button" #click.prevent="removeProductFromCart(item.id)">
<input type="checkbox" id="checkbox">
<div id="bin-icon">
<div id="lid"></div>
<div id="box">
<div id="box-inner">
<div id="bin-lines"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import cartHelper from "../helpers/cartHelper";
export default {
props: {
item: Object,
},
data() {
return {
loading: false,
};
},
methods: {
removeProductFromCart(id) {
this.loading = true;
setTimeout(() => {
cartHelper.removeFromCart(id, (response) => {
this.$store.dispatch('removeProductFromCart', {
cart: response.data,
})
this.loading = false
});
}, 1000)
}
}
};
</script>
So the parent component is ticket component:
[![<template>
<div id="sold-tickets">
<div class="card">
<div class="sold-tickets-actions properties">
<div class="sold-tickets-inner">
<DeleteButton :item = "item" />
</div>
</div>
</div>
</div>
</template>][1]][1]
<script>
import image from "../../../../img/Hallenbad.jpg";
import DeleteButton from "./DeleteButton";
import cartHelper from "../helpers/cartHelper";
export default {
props: {
item: Object,
},
components: {DeleteButton},
data() {
return {
image: image,
};
},
};
</script>
My problem is, the ticket is being deleted even when I click outside of the child component (Delete component). But I want to use the delete component like a button and I only want to delete the ticket when it is clicked, not outside of the button.
Try to stop propagation. You probably do not even need the prevent modifier.
#click.prevent.stop='...'
or
#click.stop='...'
If the display type is block, you might also want to inspect the div to see if it is actually filling the entire width of the page. If so, use an inline type, a flex layout, or similiar.
I would like to have modals and notifications in my app and coming from using old jQuery Bootstrap, creating modals and notifications were really easy but now I am pretty confused on how to implement this in the virtual DOM using the react component system.
This is what I believe the standard react way to build modals in React within a component:
Index/Router Component >
Main Layout Component >
{...Page Components... }
{...Child Component}
{<Modal /> or <Notification />}
The issue with this is I dont want to constantly have to import and create a <Modal> or <Notification /> component within my sub components, instead maybe just call a utility function such as {app.notify({type: 'success', message: 'some message'})} or app.modal({...customconfig}) and have both defined within my Main layout component which get triggerd through any child components.
Any help on this would be great, thanks!
You do not need to keep your Modal component in a hierarchy. Your Modal component should be an independent component which would take appropriate props to decide what needs to be displayed. E.g.
<Modal message={"This is my modal"} showOkCancel={true} showYesNo={false} handleOkYes={()=>console.log("OK clicked")} handleCancelNo={()=>console.log("Cancel clicked"} />
In the above example, the Modal accepts a number of props which would help it decide the message to display, the buttons to display and the actions that need to take on said button click.
This kind of a component can reside outside your component hierarchy and can be imported into any component that needs to show a modal. The parent component would just need to pass the appropriate props to show the modal.
Hope this helps.
So here is the approach I took to resolve this.
First here is how you want to structure the modal and notification components:
{Index/Router Component}
{Main Layout Component <Modal /> or <Notification />}
{...Page Components... }
{...Child Component calls app.modal({...config}) or app.notify(...config)}
For notifications, I used a plugin called react-notification-system and for modal, I just wrote it myself.
Here is my code:
Layout.js
import React from "react";
import {Link} from 'react-router';
import NotificationSystem from 'react-notification-system';
import AppHeader from "#/ui/header/AppHeader";
import AppFooter from "#/ui/footer/AppFooter";
import Modal from "#/ui/modals/modal/Modal";
import "#/main.scss";
import './layout.scss';
export default class Layout extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
app.notify.clear = this.refs.notificationSystem.clearNotifications;
app.notify = this.refs.notificationSystem.addNotification;
app.modal = this.refs.modal.updateProps;
}
render() {
return (
<div class="app">
<div class="header">
<AppHeader page={this.props.location.pathname.replace('/', '')}/>
</div>
<div class="body">
{this.props.children}
</div>
<div class="footer">
<AppFooter />
</div>
<NotificationSystem ref="notificationSystem" style={false} />
<Modal ref="modal" />
</div>
);
};
}
Modal.js
import React from "react";
import ReactDOM from 'react-dom';
import SVGInline from "react-svg-inline";
import {closeSvg} from '#/utils/Svg';
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
showHeader: true,
showFooter: false,
title: "",
size: '',
className: '',
id: '',
footerContent: null,
showSubmitBtn: true,
showCancelBtn: true,
cancelBtnText: "Cancel",
successBtnText: "Save Changes",
onModalClose: () => {},
showModal: false,
html: () => {}
}
this.updateProps = this.updateProps.bind(this);
this.hideModal = this.hideModal.bind(this);
}
componentWillMount() {
var self = this;
var $modal = $(ReactDOM.findDOMNode(this));
}
componentDidUpdate(prevProps, prevState) {
if(this.state.showModal) {
$('body').addClass('modal-open');
} else {
$('body').removeClass('modal-open');
}
}
componentWillUnmount() {
// $('body').removeClass("modal-open");
}
componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
updateProps(args) {
let merged = {...this.state, ...args};
this.setState(merged);
}
hideModal() {
this.setState({
showModal: false
});
this.state.onModalClose();
}
buildFooter() {
if(this.props.footerContent) {
return (
<div class="content">
{this.props.footerContent}
</div>
)
} else if(this.props.showCancelBtn && this.props.showSubmitBtn) {
return (
<div class="buttons">
<button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>{this.props.cancelBtnText}</button>
<button type="button" class="btn btn-success">{this.props.successBtnText}</button>
</div>
);
} else if(this.props.showCancelBtn) {
return (<button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>Close</button>);
} else if(this.props.showSubmitBtn) {
return (<button type="button" class="btn btn-success">Save changes</button>);
}
}
render() {
let {
id,
className,
onModalClose,
size,
showHeader,
title,
children,
showFooter,
showModal,
html
} = this.state;
return (
<div class={`modal-wrapper`} >
{
showModal ?
<div class={`modal fade in ${className}`} role="dialog">
<div class="bg" ></div>
<div class={`modal-dialog ${size}`}>
<div class="modal-content">
{ showHeader ?
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<SVGInline svg={closeSvg} />
</button>
<h4 class="modal-title">{ title }</h4>
</div> : '' }
<div class="modal-body" >
{html()}
</div>
{ showFooter ?
<div class="modal-footer">
{ this.buildFooter() }
</div> : ''
}
</div>
</div>
</div>
: ''
}
</div>
);
}
}
then in any of your child components you can just call within your render function:
app.notify({
message: message,
level: 'error'
});
or
app.modal({
showModal: true,
className: "fullscreen-image-modal",
size: "modal-lg",
html: () => {
return (<img src={listingManager.LISTINGS_PATH + imgUrl} />);
}
})
I'm having an issue with editing a value in my React app. I'm aware of how controlled components work, and my problem isn't related to that.
I can paste text into it and see state for the input change, but when I try to change it myself nothing happens. The input resides inside of a TableHeader component.
import React from "react";
import classNames from "classnames";
class TableHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
columnFilterText : "",
filterBoxOpen : false
}
this.toggleSortBox = this.toggleSortBox.bind(this);
this.handleColumnInputChange = this.handleColumnInputChange.bind(this);
}
toggleSortBox(event, value) {
if(event.target === event.currentTarget) {
this.setState({
filterBoxOpen: !this.state.filterBoxOpen
});
}
}
handleColumnInputChange(event) {
console.log(event)
this.setState({
columnFilterText: event.target.value
})
}
render() {
let tableHeaderClasses = classNames({
"sortable" : true,
"filter-box-open" : this.state.filterBoxOpen
});
let sortOptionClasses = classNames({
"sort-option" : true
});
return (
<th className={tableHeaderClasses} onClick={this.toggleSortBox}>
<div className="sort-box">
<div className="sort-option-container">
<div className={sortOptionClasses}>Sort - ascending</div>
</div>
<div className="sort-option-container">
<div className={sortOptionClasses}>Sort - descending</div>
</div>
<hr className="divider" />
<input onChange={this.handleColumnInputChange} type="text" value={this.state.columnFilterText} />
<div className="row">
<button className="six columns">Apply</button>
<button className="six columns">Clear</button>
</div>
</div>
{this.props.label}
</th>
);
}
}
export default TableHeader;
I've checked if it's a css issue, by disabling styles, but the input field is still disabled, so no luck there. Any ideas what might be causing the issue?
I'm an idiot. The reason for this was that I had a keyDown event on the parent component for keyboard navigation, with event.preventDefault(); which of course affected the child component.
Thanks to everyone for contributing and for trying to help!
I've been modifying the Meteor1.3+React Todos app to get the basics down and it's been going well so far. However, i'd like to add another text field so that the user can submit a description of an item (the first field that comes with the Todos app) as well as the cost of that item. I've been trying to add the second input field and copy over the values/pass them through to the tasks.js api but I can't seem to get it to work. I'm aware that this is aesthetically unsettling (hitting enter to input two text fields into a collection) and it may be impossible/is most likely not the correct way to do something like this.
Here's what I'm working with:
App.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import Task from './Task.jsx';
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
// App component - represents the whole app
//render collection of tasks
class App extends Component {
//componenet contructor that contains initializations for: hideCompleted
constructor(props) {
super(props);
this.state = {
hideCompleted: false,
};
}
//Event handler for when you press enter to input data. Calls Meteor method tasks.insert and sends it the text,
//then clears the text form
handleSubmit(event) {
event.preventDefault();
// Find the task text field via the React ref
const taskText = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
// Find the cost field via the React ref
const costNum = ReactDOM.findDOMNode(this.refs.costInput).value.trim();
//Call the tasks insert method in tasks.js api
Meteor.call('tasks.insert', taskText, costNum);
// Clear task form
ReactDOM.findDOMNode(this.refs.textInput).value = '';
//Clear cost form
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
//Event handler for hideCompleted checkbox check
toggleHideCompleted() {
this.setState({
hideCompleted: !this.state.hideCompleted,
});
}
//Filters out tasks that have hideCompleted === true
renderTasks() {
let filteredTasks = this.props.tasks;
if (this.state.hideCompleted) {
filteredTasks = filteredTasks.filter(task => !task.checked);
}
return filteredTasks.map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>The Economy</h1> ({this.props.incompleteCount})
<label className="hide-completed">
<input
type="checkbox"
readOnly
checked={this.state.hideCompleted}
onClick={this.toggleHideCompleted.bind(this)}
/>
Hide Completed Tasks
</label>
<AccountsUIWrapper />
{ this.props.currentUser ?
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks"
/>
<input
type="Number"
ref="costInput"
placeholder="Type to add cost"
/>
</form> : ''
}
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
}
//proptypes - set up the tasks proptype
App.propTypes = {
tasks: PropTypes.array.isRequired,
incompleteCount: PropTypes.number.isRequired,
currentUser: PropTypes.object,
};
//exports createContainer function which queries the tasks collection
export default createContainer(() => {
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
currentUser: Meteor.user(),
// currentBalance: Tasks.characters.aggregate([ { $group: { _id: null, total: { $sum: "$cost" } } } ]),
};
}, App);
tasks.js
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const Tasks = new Mongo.Collection('tasks');
Meteor.methods({
'tasks.insert'(text, cost) {
//Make sure that text is a String
check(text, String);
//Make sure that cost is a Number
check(cost, Number);
// Make sure the user is logged in before inserting a task
if (! this.userId) {
throw new Meteor.Error('not-authorized');
}
//Actual database insertion
Tasks.insert({
text,
cost,
createdAt: new Date(),
owner: this.userId,
username: Meteor.users.findOne(this.userId).username,
});
},
'tasks.remove'(taskId) {
check(taskId, String);
Tasks.remove(taskId);
},
'tasks.setChecked'(taskId, setChecked) {
check(taskId, String);
check(setChecked, Boolean);
Tasks.update(taskId, { $set: { checked: setChecked } });
},
});
I feel like there's some type of simple answer out there but after a while of searching, I can't seem to find anything that makes sense to me. I haven't dabbled in any React forms packages yet because I thought i'd be able to do this without one. I feel bad about asking about something seemingly so simple but alas here I am. Any recommended reading or methods to look into is greatly appreciated.
I think you forgot to add a button to a form
<input type="submit" value="Submit my form" />
I have an ecosystem of 4 inter-related components and it'd be very helpful if I can manipulate one component's state from a different one.
This is my MidSection component:
import React, {Component} from 'react';
import {render} from 'react-dom';
import $ from 'jquery';
import List from './List';
import OpenSessionCard from './OpenSessionCard';
class MidSection extends Component {
constructor() {
super(...arguments);
this.state = {
cardsToBeDisplayed: this.props.sessionCards,
cardsFilter: 'All',
cardExpanded: false,
cardToBeDisplayed: null
};
}
filterCards() {
let selectedValue = $('#cards-filter').val();
if (selectedValue === 'All') {
this.setState({
cardsToBeDisplayed: this.props.sessionCards,
cardsFilter: 'All',
cardExpanded: false,
cardToBeDisplayed: null
});
} else {
this.setState({
cardsToBeDisplayed: this.props.sessionCards.filter((sessionCard) => sessionCard.status === selectedValue),
cardsFilter: selectedValue,
cardExpanded: false,
cardToBeDisplayed: null
});
}
}
render() {
let cardList, openSessionCard;
if (!this.state.cardToBeDisplayed) {
cardList = (
<List cards={this.state.cardsToBeDisplayed} filter={this.state.cardsFilter}/>
);
} else {
openSessionCard = (
<OpenSessionCard card={this.state.cardToBeDisplayed}/>
);
};
return (
<section className="col-md-8 col-sm-12 col-xs-12 middle-section-container">
<div className="nav-justified pull-left">
<div className="gray-background-color col-xs-12 form-control-static">
<div className="col-xs-6">
<label className="control-label green-color" htmlFor="inputError1">MY SESSIONS</label>
</div>
<div className="col-xs-2 col-xs-offset-4 text-right">
<select id="cards-filter" className="form-control" onChange={this.filterCards.bind(this)}>
<option>All</option>
<option>Open</option>
<option>Scheduled</option>
<option>Completed</option>
<option>Closed</option>
</select>
</div>
</div>
{cardList}
{openSessionCard}
</div>
</section>
);
}
componentDidMount() {
$('#mid-section').attr('data-rendered', 'true');
}
}
export default MidSection;
And this is my SessionCard component:
import React, {Component} from 'react';
import {render} from 'react-dom';
import $ from 'jquery';
import MidSection from './MidSection';
class SessionCard extends Component {
openCard() {
/*************
MidSection.setState({
cardsToBeDisplayed: null,
cardsFilter: null,
cardExpanded: true,
cardToBeDisplayed: this
});
**************/
$('#cards-filter').attr('disabled', 'disabled');
}
render() {
return (
<div className="card" onClick={this.openCard.bind(this)}>
<div className="card__title green-color">{this.props.name}</div>
<div className="card__details">
<span>Facilitator: {this.props.facilitator}</span><br/>
<span>Mode: {this.props.mode}</span><br/>
<span>Status: {this.props.status}</span><br/>
</div>
</div>
);
}
}
export default SessionCard;
I want the openCard() function in the SessionCard component to call the setState() function of the MidSection component. Is there any way I can achieve this? How do I refer to the MidSection component (with its current state) from the SessionCard component?
You should only set state from the main (parent) component. All children components should be "dumb" components. Pass in the function you are wanting to call as a prop of SessionCard like :
<OpenSessionCard card={this.state.cardToBeDisplayed} setStateFunc={this.setStateFunc}/>
And then in your openCard() function call :
this.props.setStateFunc();
This will call the function in the parent component and allow you to manipulate the state from there.
Thanks a lot #erichardson30! Even though your solution didn't totally suffice, yet it guided me to the right track. Passing the setState() function as a prop only passed the function, without the context (read: the component on which it is supposed to be called). So, I passed a reference to the MidSection component itself as a prop, and then in the openCard() function, I invoked the setState() function of the component in its props.
In my MidSection component:
<OpenSessionCard card={this.state.cardToBeDisplayed} midSectionComponent={this}/>
And in my openCard() function:
let midSectionComponent = this.props.midSectionComponent;
midSectionComponent.setState({
cardsToBeDisplayed: null,
cardsFilter: null,
cardExpanded: true,
cardToBeDisplayed: this
});
It's working exactly the way I wanted it to. Thanks again! :)