Pushing data to object in different component using POST - wordpress

TL;DR I want to show submitted posts instantly instead of having to refresh my page
Using the Wordpress REST API I am able to create a new post without any issue. The post is being displayed as soon as the page refreshes, so what I want to do is update the posts object in my Hello.vue file as soon as I create that post so I don't need to refresh to show my newest posts.
I'm not really sure where to start - I've removed all of the experiments I've done so far (importing Post in Create, defining props, pushing to an array, reading about object reactivity on the official Vue documentation, nothing helped).
My App.js consists of the <router> object which shows Hello.vue and a component called Create which displays the Create.vue component. This is how my app currently looks like:
My App.vue file:
<template>
<div id="app">
<section class="posts">
<router-view></router-view>
<create></create>
</section>
</div>
</template>
<script>
import Create from '#/components/Create.vue'
export default {
name: 'app',
components: {
Create
}
}
</script>
<style lang="scss">
#import '../src/assets/styles/style.scss'
</style>
My Hello.vue which displays all the posts:
<template>
<div>
<section class="posts__Feed">
<ul class="posts__List">
<post v-for="item in posts" :item="item" :key="item.id"></post>
</ul>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
import Post from '#/components/Post.vue'
export default {
name: 'hello',
props: ['responseData'],
components: {
Post
},
data () {
return {
posts: []
}
},
beforeCreate () {
this.$http.get(postsUrl).then((response) => {
this.posts = response.data
})
}
}
</script>
And finally, the Create.vue file which creates the post:
<template>
<div>
<section class="posts__Create">
<form class="posts__CreateForm" v-on:submit="createPosts">
<div class="posts__CreateFormWrapper" v-bind:class="{ 'is-Loading': loading }">
<p>
<input v-model="formInfo.title" type="text" name="title" id="title" placeholder="Name" :disabled="formSent">
</p>
<p>
<textarea v-model="formInfo.content" name="content" id="content" cols="20" rows="10" maxlength="140" placeholder="Message" :disabled="formSent"></textarea>
</p>
<p>
<button :disabled="formSent">Send</button>
</p>
</div>
</form>
</section>
</div>
</template>
<script>
var postsUrl = '/wp-json/wp/v2/posts/'
export default {
name: 'create',
data () {
return {
formInfo: [],
responseData: [],
loading: false,
formSent: false
}
},
methods: {
createPosts (e) {
e.preventDefault()
var info = this.formInfo
// Check if fields are empty
if (this.formInfo.title && this.formInfo.content) {
this.loading = true
// POST
this.$http.post(postsUrl, info).then((response) => {
this.formSent = true
this.loading = false
// get body data
this.responseData = response.data
})
}
} // EOF createPosts
}
}
</script>
Any help would be much appreciated!

I ended up using an event bus as suggested by wotex. First, I've createad a file called bus.js with the below code:
import Vue from 'vue'
export const EventBus = new Vue()
Next, import bus.js to both .vue layouts using:
import { EventBus } from '#/bus.js'
Now emit the event as soon as a new post is created (this is sitting in my axios POST request inside the Create.vue file):
EventBus.$emit('newPost', this.responseData)
And finally, check if the event has happened on the other end (my Hello.vue file):
EventBus.$on('newPost', function (postData) {
Thanks for pointing me in the right direction!

Related

VUE3.JS - Modal in a loop

I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.

Passing props that is an array to 2 different components

This is my 3rd day working with Vue CLI, and I want to pass props that is an array to 2 different components. The components are called Products and Modal. The Modal component is dependent on the Products component. When I pass props to both components inside my App.vue, I don't want both components to render in my App.vue. Like I said, my Modal component is dependent on my Products component.
My overall goal is to display the product title, photo, and description in my modal.
The issue I have is I make a get request in my App.vue, which i fill an empty array and then pass that to Products component. If i pass the array as props to both Products and Modal, I will get an extra modal to render at app level which does make sense, but I don't want that.
Im still getting used to how Vue works and tips or help would be much appreciated.
This could be a very obvious answer that i'm just overlooking, but i'm learning, so please understand that.
App.Vue (I make my get request here and fill an empty array to then have that array passed down as props to the two components):
<template>
<div id="app">
<all-products v-bind:newProductsArray="newProductsArray"></all-products>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import AllProducts from './components/AllProducts.vue'
import Modal from './components/Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
//export components to be rendered by main.js then by the DOM
export default {
components: {
'all-products': AllProducts
},
data() {
return {
//empty products array to be filled after get request
products: [],
//declare new array to be set to products array, then passed as props. I do this because without another array,
//all products component will receiev an empty array before it can be filled by the http request.
newProductsArray: []
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
//set products array to the new array to be passed down as props to all products component.
this.newProductsArray = this.products
})
}
}
</script>
Product component (This component receives props from app.vue):
<template>
<div class="products">
<h1 class="all-products">All Products</h1>
<div v-for="product in newProductsArray" :key="product.id" class="single-product">
<div class="product-container">
<div class="row">
<div class="col-md-8 center-block">
<div class="title">{{product.title}}</div>
<img class="images" :src="product.photo_url">
<div class="price">${{product.price}}</div>
<modal/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import bootstrap to be used to style buttons and modal.
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
export default {
//receive new products array as props from app
props: ['newProductsArray'],
components: {
'modal': Modal
},
data() {
return {
//set modal show to false by default
modalShow: false
}
},
methods: {
showModal() {
//toggle modal to show
this.modalShow = true
},
closeModal() {
//toggle modal to close
this.modalShow = false
}
},
}
</script>
Modal component (I want to receive the same props that products received)
<template>
<div>
<b-button #click="modalShow = !modalShow">Show Product Info</b-button>
<b-modal v-model="modalShow" title="title">
<div class="modal-body">
<img class="img-responsive" style="margin:0 auto;" src="http://placehold.it/300x340" alt="">
</div>
<p class="product-description">Description</p>
<div slot="modal-footer" class="w-100">
<b-button size="md" class="float-right" variant="warning" #click="modalShow=false">Close</b-button>
<b-button size="md" class="float-right" variant="primary" #click="modalShow=false">Like</b-button>
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: ['newProductsArray'],
data() {
return {
modalShow: false
}
}
}
</script>

Calling Meteor methods in React components

Currently I'm working on a project based on Meteor as back end and React as front end. I really enjoyed simplicity untill I removed insecure package and have to deal with Meteor methods. Right now I need to perform a basic insert operation and I'm just stucked!
I have a form as component (in case eventually I'd like to use this form not only for inserting items but for editing those items as well) and here's my code for this form:
AddItemForm = React.createClass({
propTypes: {
submitAction: React.PropTypes.func.isRequired
},
getDefaultProps() {
return {
submitButtonLabel: "Add Item"
};
},
render() {
return (
<div className="row">
<form onSubmit={this.submitAction} className="col s12">
<div className="row">
<div className="input-field col s6">
<input
id="name"
placeholder="What"
type="text"
/>
</div>
<div className="input-field col s6">
<input
placeholder="Amount"
id="amount"
type="text"
/>
</div>
</div>
<div className="row">
<div className="input-field col s12">
<textarea
placeholder="Description"
id="description"
className="materialize-textarea">
</textarea>
</div>
</div>
<div className="row center">
<button className="btn waves-effect waves-light" type="submit">{this.props.submitButtonLabel}</button>
</div>
</form>
</div>
);
}
});
This chunk of code is used as a form component, I have a prop submitAction which I use in let's say add view:
AddItem = React.createClass({
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Items.insert(
{
name: name,
range: range,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
},
function(error) {
if (error) {
console.log("error");
} else {
FlowRouter.go('items');
};
}
);
},
render() {
return (
<div className="row">
<h1 className="center">Add Item</h1>
<AddItemForm
submitButtonLabel="Add Event"
submitAction={this.handleSubmit}
/>
</div>
);
}
});
As you can see I directly grab values by IDs then perform insert operation which works absolutely correct, I can even get this data displayed.
So now I have to remove insecure package and rebuild the whole operation stack using methods, where I actually stucked.
As I understand all I should do is to grab same data and after that perform Meteor.call, but I don't know how to pass this data correctly into current method call. I tried considering this data right in the method's body which doesn't work (I used the same const set as in AddItem view). Correct me if I'm wrong, but I don't think this method knows something about where I took the data (or may be I don't really get Meteor's method workflow), so by this moment I ended up with this code as my insert method:
Meteor.methods({
addItem() {
Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
}
});
and this is how I changed my handleSubmit function:
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem');
},
Also I tried declaring method like this:
'addItem': function() {
Items.insert({
// same code
});
}
but it also didn't work for me.
Again, as I understand the problem isn't about data itself, as I wrote before it works just right with insecure package, the problem is how the heck should I get this data on the server first and right after that pass this to the client using methods (also console gives no even warnings and right after I submit the form, the page reloads)?
I've already seen some tutorials and articles in the web and didn't find desicion, hope to get help here.
You can add your data as parameters in your Meteor call function. You can also add a callback function to check on the success of the call.
handleSubmit(event) {
event.preventDefault();
const
name = $('#name').val(),
amount = $('#amount').val(),
description = $('#description').val();
Meteor.call('addItem', name, amount, description, function(err, res) {
if (err){
console.log(JSON.stringify(err,null,2))
}else{
console.log(res, "success!")
}
});
},
In your Meteor methods:
Meteor.methods({
addItem(name, amount, description) {
var Added = Items.insert({
name: name,
amount: amount,
description: description,
createdAt: new Date(),
ownerId: Meteor.userId()
});
return Added
}
});

Meteor, Flow Router, and React: How to get FlowRouter.subsReady() function to be reactive in render

I am trying to create a pattern so that all the subscriptions are ready before I load the main page. Similar to Iron Router waitOn.
Take a look at this react component:
export const PageContainer = React.createClass({
render() {
return (
<div id="content-box">
<div className="banner banner-primary">
<div className="page_title pull-left">
{this.props.pageName}
</div>
</div>
<div>
{ FlowRouter.subsReady() ? this.props.page : (
<div> Loading .... </div>
)
}
</div>
</div>
);
}
});
as you can see I am using the FlowRouter.subsReady() helper to render the page or the loading text.
The problem is that this is not reactive. It just renders once but does not update and show the page once the subscription is ready.
How can I get this to be reactive?
What is the best way to use Flow Router's subscription management with React. I have a base layout and want to show loading sign before loading the page main. If I could get this function to be reactive it should work just fine.
UPDATE:
It seems like I have to attach the helper, FlowRouter.subsReady() to the get Meteor data function
export const PageContainer = React.createClass({
mixins: [ ReactMeteorData ],
getMeteorData() {
return {
isLoading: FlowRouter.subsReady()
}
},
render() {
return (
<div id="content-box">
<div className="banner banner-primary">
<div className="page_title pull-left">
{this.props.pageName}
</div>
<i className="fa fa-question-circle help-icon pull-right"></i>
</div>
<div>
{ this.data.isLoading ? this.props.page : (
<div> Loading ... </div>
)
}
</div>
</div>
);
}
});
It seems to be working now. Is this the way to do it?
You accessed the problem in the wrong direction. You don't really need to check the subsReady of FlowRouter when using meteor with react. Just install the mixin ReactMeteorData and set the this.data properly, it will reactively render the Dom. More details here
React render is not reactive. The Dom is only re-rendered when the props or state of the component is changed

getting this npm react component to work with meteor

Using react-typeahead-component
I used browserify to change it from npm into a meteor local package. Nothing is showing on the screen when i run meteor.
OptionTemplate.jsx
module.exports = React.createClass({
render: function() {
return (
<div>
<p>HELLO</p>
</div>
);
},
handleChange: function(event) {
console.log('HELLO');
}
});
main.jsx
var OptionTemplate = require('./OptionTemplate.jsx');
SearchBox = React.createClass({render() {
return (
<div className="col-xs-12 col-lg-12">
<OptionTemplate />
</div>
)}
});
You aren't using components properly. You might want to re-read the react documentation.
In your main.jsx, you are importing the component 'OptionTemplate' but you are trying to render the component 'Typeahead' and passing in 'OptionTemplate' as a prop. The 'Typeahead' component now lives in 'OptionTemplate'. Your main.jsx should like like:
var OptionTemplate = require('./OptionTemplate.jsx');
SearchBox = React.createClass({render() {
return (
<div className="col-xs-12 col-lg-12">
<OptionTemplate />
</div>
)}
});

Resources