I have created additional submit modal in order to receive user confirmation after he decides to delete the post from collection and I cant figure out how to target the post.
Another thing that I would like to ask you is a productivity question, is it wise to insert DeletePost component into each post component or there is a way to have it inserted inside currentPage component and somehow bind the modal call to to the post.
Here is the code for DeletePost component:
class DeletePost extends Component {
handleDelete(event) {
event.preventDefault();
Meteor.call('posts.remove', post);
$('#modalDelete').modal('hide');
}
render() {
return (
<div className="modal fade form-delete" id="modalDelete" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="form-outer">
<form id='delete_post' onSubmit={this.handleDelete.bind(this)}>
<div className='form-text form-header'>
<p>My dear, <strong>master</strong></p>
<p>Are you really sure about that?</p>
</div>
<button type="button" className="form-button button-delete" data-dismiss="modal">No</button>
<button type="sumbit" className="form-button button-delete">Yes</button>
</form>
</div>
</div>
);
}
}
And here is code for the Post component which imports from DeletePost:
class PostsList extends Component {
renderData(){
return this.props.posts.map(post => {
const {title, social, link, link_image, time=moment(post.createdAt).fromNow()} = post;
return (
<div key={post._id} className='social-post'>
<img src={link_image}></img>
<p>{social}, {time}</p>
<a className='social-link' target="_blank" href={link}>{title}</a>
<div className='list-buttons'>
<button className='form-button button-gradient'>Edit</button>
<button type="button" className='form-button button-gradient' data-toggle="modal" data-target="#modalDelete">Delete</button>
</div>
<DeletePost />
</div>
);
})
}
render() {
return (
<div className='flex-timeline'>
{this.renderData()}
</div>
);
}
}
You have to pass the post value from its parents to child:
Your PostsList Class need to pass post value to child.
class PostsList extends Component {
renderData(){
return this.props.posts.map(post => {
const {title, social, link, link_image, time=moment(post.createdAt).fromNow()} = post;
return (
<div key={post._id} className='social-post'>
<img src={link_image}></img>
<p>{social}, {time}</p>
<a className='social-link' target="_blank" href={link}>{title}</a>
<div className='list-buttons'>
<button className='form-button button-gradient'>Edit</button>
<button type="button" className='form-button button-gradient' data-toggle="modal" data-target="#modalDelete">Delete</button>
</div>
<DeletePost post={post}/>
</div>
);
})
}
render() {
return (
<div className='flex-timeline'>
{this.renderData()}
</div>
);
}
}
Your DeletePost use this.props.post to access data from parent.
class DeletePost extends Component {
handleDelete(event) {
event.preventDefault();
Meteor.call('posts.remove', this.props.post);
$('#modalDelete').modal('hide');
}
render() {
return (
<div className="modal fade form-delete" id="modalDelete" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="form-outer">
<form id='delete_post' onSubmit={this.handleDelete.bind(this)}>
<div className='form-text form-header'>
<p>My dear, <strong>master</strong></p>
<p>Are you really sure about that?</p>
</div>
<button type="button" className="form-button button-delete" data-dismiss="modal">No</button>
<button type="sumbit" className="form-button button-delete">Yes</button>
</form>
</div>
</div>
);
}
}
Related
I have a list of objects named favs.
I have used mapping to show them into table form and also i have attached a button.In that button I have created a onClick function which will print the current name (using console.log() ).
but when i click on any button ,it always prints the first element of that list.
can any one look into my code and tell what am i doing wrong? thanks in advance :)
fav list
const [favs, setFavs] = useState([{
name:"node"
},
{
name:"react-js"
},
{
name:"node js"
}])
HasFav.js File
import React, { useState } from "react";
import DeleteConfirm from "./DeleteConfirm";
import { Link } from "react-router-dom";
const HasFavs = ({favs,handleDelete}) => {
const handDel =(e)=>{
console.log(e)
handleDelete(e)
}
return (
<span>
<h1>Welcome to Favorite NPM Packages</h1>
<Link to="/">Add Fav </Link>
<div>
<table>
<tr>
<th>Package Name</th>
<th>Actions</th>
</tr>
{
favs.map((favorite)=>{
return (
<tr key={favorite.name}>
<td>
{favorite.name}
</td>
<td>
<button onClick={()=>console.log(favorite.name)}>press</button>
<DeleteConfirm value={`${favorite.name}`} handleDelete={handDel} />
</td>
</tr>
)
})
}
</table>
</div>
</span>
);
};
export default HasFavs;
DeleteConfirm.js
import React, { useState } from 'react'
const DeleteConfirm = ({value,handleDelete}) => {
const del= (e)=>{
console.log(value)
console.log(e.target)
handleDelete()
}
return (
<div>
<button type="button" data-toggle="modal" data-target="#exampleModalCenter">
<i class='fa fa-trash' style={{color:'#555553'}}></i>
</button>
<div className="modal fade" id="exampleModalCenter" tabIndex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div className="modal-dialog modal-dialog-centered" role="document">
<div className="modal-content">
<div className="modal-body">
Are you sure You want to delete?
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" className="btn btn-primary" value={value} onClick={del}>Yes</button>
</div>
</div>
</div>
</div>
</div>
)
}
export default DeleteConfirm
it always prints the first obejct of favs.
Try to wrap this console.log(favorite.name) into braces {console.log(favorite.name)}
I want to load all companies via AJAX request into a state property when the user clicks on the select box.
This is the code:
import React, { Component } from 'react';
import SelectOption from './SelectOption';
class CreateFreightEntryModal extends Component {
constructor(props) {
super(props);
this.state = {
freights: props.freights,
onClose: props.onClose,
onClick: props.onClick,
companies: [],
};
}
loadCompanies(event) {
$.ajax({
type: "POST",
context:this,
dataType: "json",
async: true,
url: "../data/get/json/companies",
data: ({
_token : window.Laravel.csrfToken,
}),
success: function (data) {
var arr = $.map(data, function(el) { return el; });
this.setState({
companies: arr
})
}
});
}
render() {
return (
<div className="modal fade" id="createFreightEntryModal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 className="modal-title">New freight entry</h4>
</div>
<div className="modal-body">
<div>
<div>
<form onSubmit={this.add.bind(this)}>
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong>Create a new freight entry:</strong>
</div>
</div>
<div className="row">
<div className="col-xs-4 col-sm-4 col-md-4 col-lg-4">
Company
</div>
<div className="col-xs-8 col-sm-8 col-md-8 col-lg-8">
<div className="form-group" onClick={this.loadCompanies.bind(this)}>
<select className="selectpicker show-tick form-control" data-live-search="true" data-title="Please select" ref="Firma" required>
{
this.state.companies.map((company)=> {
return (
<SelectOption value={company.Nummer} displayValue={company.Bezeichnung} key={company.id} />
);
})
}
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="form-group">
<button type="submit" className="btn btn-success"><span className="glyphicon glyphicon-floppy-disk"></span> Save </button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
);
}
}
export default CreateFreightEntryModal
When I add the componentWillReceiveProps(nextProps) method, I get this error.
This error occurs when the page is loaded and not when I click on the select box!
componentWillReceiveProps(nextProps) {
this.setState({
companies: nextProps.companies,
});
}
TypeError: this.state.companies is undefined
This is the part where the error occurs:
...
this.state.companies.map((company)=> {
...
How can I solve this issue? Thanks for your help in advance.
Using this construct:
componentWillReceiveProps(nextProps) {
this.setState({
companies: nextProps.companies,
});
}
you update state.companies every time the component receives ANY props, even when there are no companies in the props. And when the nextProps don't have companies it is set to undefined.
Let's illustrate it this way:
{
let props = { freights : [ 'a', 'b', 'c' ] }
this.setState({ companies : props.companies })
/* state.companies are now undefined, because props.companies are undefined */
}
Fix:
componentWillReceiveProps(nextProps) {
if( nextProps.companies ){
this.setState({
companies: nextProps.companies,
});
}
}
BTW the problem with success callback scope I have mentioned in a comment may still apply.
I have a php page person.php that includes 2 vueJs components: person-details.vue and phones.vue. Each of these components include the same third component notify-delete. This notify-delete component includes a bootstrap modal dialog to confirm a deletion action of the parent component (person or phone).
I use props to set a message in the modal confirmation dialog and $refs to show it.
Problem:
When this props is set the msg props from the component person and the dialog is shown, the message is correctly set. However when I set from the phones component, the dialog shows the message set by person. as if person is constantly overriding the value of the msg props.
here is a sample of the code:
person.php:
<person-details details="<?= json_encode($person) ?>"></person-details>
<phones details="<?= json_encode($phones) ?>"></phones>
Person-details.vue:
<template>
<notify-delete ref="modalDeletePerson" :entity="'person'" :msg="deleteMsg" #confirmed="deleteMe"></notify-delete>
<button type="button" #click="confirmDelete">Delete this person</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this person?'
this.$refs.modalDeletePerson.show()
},
deleteMe() {
// delete person
}
}
}
</script>
</template>
Phones.vue:
<template>
<notify-delete ref="modalDeletePhone" :entity="'phone'" :msg="deleteMsg" #confirmed="deleteMe($event)"></notify-delete>
<button type="button" #click="confirmDelete">Delete this phone</button>
</template>
<script>
export default {
data () {
return {
deleteMsg: ''
}
},
methods: {
confirmDelete() {
this.deleteMsg = 'Are you sure you want to delete this phone?'
this.$refs.modalDeletePhone.show()
},
deleteMe() {
// delete phone
}
}
}
</script>
Notify-delete.vue:
<template>
<div id="modalDelete" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
{{msg}}
</div>
<div class="modal-footer">
<button type="submit" data-dismiss="modal" #click="confirm">Delete</button>
<button type="button" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['entity', 'msg'],
methods: {
show() {
$('#modalDelete').modal('show')
},
confirm() {
this.$emit('confirmed')
}
}
}
</script>
Any idea how I can have two distinguish instances of the same component?
The problem is here
show() {
$('#modalDelete').modal('show')
}
You are rendering two modals with the same id, then using jQuery to show them. Specifically, $('#modalDelete') will contain two elements. I expect the modal method just picks the first one and shows it.
Try
<div ref="modal" class="modal fade" tabindex="-1" role="dialog">
and
show() {
$(this.$refs.modal).modal('show')
}
This should give each instance of the Notify-delete.vue component it's own reference.
I have 2 components:
Vue.component('repo-button', {
props:["check_in_id", "repo_id"],
template: '#repo-button',
methods: {
fetchRepo: function() {
url = window.location.href.split("#")[0] + "/check_ins/" + this.check_in_id + "/repositionings/" + this.repo_id + ".json"
cl(url)
cl(this)
var that;
that = this;
$.ajax({
url: url,
success: function(data) {
cl(data)
that.showRepo();
}
})
},
showRepo: function() {
// what do I put here to display the modal
}
},
data: function() {
var that = this;
return {
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
data: function() {
return {
status: 'none'
}
}
});
var repositionings = new Vue({
el: "#repo-vue"
});
...and my view consists of a button and a modal. I'd like the button to call fetchRepo on the repo-button component and display the modal (change its status property from none to block.
<script type="text/x-template" id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="fetchRepo"
:data-check_in='check_in_id'
:data-repo='repo_id'>
</i>
</div>
</script>
<script type="text/x-template" id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-client_id="<%= #client.id %>">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</script>
<div id="repo-vue">
<div is="repo-modal"></div>
<div is="repo-button" repo_id="<%= ci.repositioning.id %>" check_in_id="<%= ci.id %>"></div>
</div>
Props down, events up
In Vue.js, the parent-child component relationship can be summarized
as props down, events up. The parent passes data down to the child via
props, and the child sends messages to the parent via events.
In particular, if the state of a component needs to be controlled externally (by a parent or sibling), that state should be passed in as a prop from the parent. Events indicate to the parent that the state should be changed.
Your modal's state is controlled by events in itself and in a sibling component. So the state lives in the parent, and is passed to the modal as a prop. Clicking the modal Close button emits a (custom) hidemodal event; clicking the sibling component's comment icon emits a showmodal event. The parent handles those events by setting its showRepoModal data item accordingly.
Vue.component('repo-button', {
template: '#repo-button',
methods: {
showRepo: function() {
this.$emit('showmodal');
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
props: ["show"],
computed: {
status() {
return this.show ? 'block' : 'none'
}
},
methods: {
hideRepo() {
this.$emit('hidemodal');
}
}
});
var repositionings = new Vue({
el: "#repo-vue",
data: {
showRepoModal: false
}
});
.socialCircle-item i {
cursor: pointer;
}
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="showRepo"
>
</i>
</div>
</template>
<template id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" >
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" #click="hideRepo" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>
<div id="repo-vue">
<div is="repo-modal" :show="showRepoModal" #hidemodal="showRepoModal = false"></div>
<div is="repo-button" #showmodal="showRepoModal = true"></div>
</div>
When I run this code, I get a bootstrap panel group for each recipe item inside of local storage. When I try to delete a recipe, sometimes the right recipe is removed and sometimes not. The console shows that the right recipe is removed from local storage but for some reason when the app component resets its state, the wrong recipe is removed. I've noticed that if I try to delete the recipes from the bottom up, it works. But if I click on the first recipe, the bottom recipe is removed.
I know this is an easy fix but I need a fresh perspective. Thanks everyone!
Also, sorry lack of indentation in the code - stack overflow wasn't being too friendly with the spacing
class App extends Component {
constructor() {
super();
this.deleteRecipe = this.deleteRecipe.bind(this)
this.state = {
recipeData: JSON.parse(localStorage.getItem('recipeData'))
}
}
deleteRecipe() {
this.setState({recipeData: JSON.parse(localStorage.getItem('recipeData'))})
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React Recipe Box!</h2>
</div>
<div className="container">
{this.state.recipeData.map(recipe => {
return (
<Recipe name={recipe.name} ingredients={recipe.ingredients} deleteRecipe={this.deleteRecipe}/>
)
})}
</div>
</div>
);
}
}
class Recipe extends Component {
constructor() {
super();
this.onDeleteRecipe = this.onDeleteRecipe.bind(this)
}
componentWillMount(){ //set state when component is about to mount
this.state = {
name: this.props.name,
ingredients: this.props.ingredients,
}
}
onDeleteRecipe() {
var recipeList = JSON.parse(localStorage.getItem('recipeData'));
for(var i = 0; i < recipeList.length; i++) {
if(recipeList[i].name === this.state.name) {
recipeList.splice(i, 1);
console.log("Deleted " + this.state.name, recipeList);
localStorage.removeItem('recipeData');
localStorage.setItem('recipeData', JSON.stringify(recipeList));
this.props.deleteRecipe();
}
}
}
render() {
return (
<div>
<div className="panel-group">
<div className="panel panel-primary">
<div className="panel-heading">
<h2 className="panel-title">
<a data-toggle="collapse" data-target={'#' + (this.state.name).replace(/\s/g, '')} href={'#' + (this.state.name).replace(/\s/g, '')}>
{this.state.name}
</a>
</h2>>
</div>
<div id={(this.state.name).replace(/\s/g,'')} className="panel-collapse collapse">
<div className="panel-body">
{this.state.ingredients.map(ingredient => {
return <li className="list-group-item">{ingredient}</li>
})}
<div className="btn-group">
<button className="btn btn-sm btn-info" data-toggle="modal"
data-target={'#' + (this.state.name).replace(/\s/g, '') + 'EditModal'}>Edit</button>
<button className="btn btn-sm btn-danger" data-toggle="modal"
data-target={'#' + (this.state.name).replace(/\s/g, '') + 'RemoveModal'}
>Delete</button>
</div>
</div>
</div>
</div>
</div>
<div className="modal modal-lg" id={(this.state.name).replace(/\s/g, '') + 'EditModal'} >
<div className="modal-content">
<div className="modal-header">
<h2>Edit {this.state.name}</h2>
</div>
<div className="modal-body">
<ul className="list-group list-unstyle">
{this.state.ingredients.map( ingredient => {
return <li className="list-group-item">{ingredient}</li>
})}
</ul>
</div>
<div className="modal-footer">
<div className="btn-group">
<button className="btn btn-sm btn-info" data-dismiss="modal">Save</button>
<button className="btn btn-sm btn-danger" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div className="modal modal-lg" id={this.state.name.replace(/\s/g, '') + 'RemoveModal'}>
<div className="modal-content">
<div className="modal-body">
<h3>This will remove the selected recipe. Are you sure?</h3>
</div>
<div className="modal-footer">
<div className="btn-group">
<button className="btn btn-sm btn-danger" data-dismiss="modal" onClick={this.onDeleteRecipe}>Delete</button>
<button className="btn btn-sm btn-info" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default App;
I'm still a novice, but... I built a react recipe box for FreeCodeCamp a few months ago, so I just pulled it up to compare my delete function to yours.
I notice that you treat localStorage differently than I did in mine. (Not that my way is best or even right!) I wonder if somehow the problem is rooted in that design. Your delete function goes into localStorage and makes changes, then you run a setState to sort of "re-get" the recipeData, if I'm reading right?
By contrast, my app declares a variable from localStorage and I set this.state.recipeArray to equal that variable. Then all of my edit/add/delete functions change this.state.recipeArray, not localStorage. Sorta like this:
handleDelete: function(e) {
e.preventDefault();
var replacementRecipeArray = this.state.recipeArray.filter((recipe) => recipe.title !== e.target.name);
//dot-filter with ES6 fat-arrow returns all items where title doesn't match the one we clicked; IOW, it removes the one we want to delete
this.setState({
recipeArray: replacementRecipeArray
});
}
In order to get any changes to this.state.recipeArray back to localStorage, I do a localStorage.setItem every time I render the page.
render() {
//first save current array to localstorage - wasn't reliable anywhere else
localStorage.setItem("_shoesandsocks_recipes", JSON.stringify(this.state.recipeArray));
//render page
return (
<div className="App">
<div className="recipeContainer">
// etc etc
For all I know this is a crazy design, but it works.