How to properly subscribe to collection on Meteor client side? - mongodb

First of all, I'm not a newbie to Meteor, but after the latest Meteor updates I have to re-study the framework, and now I'm having trouble using Meteor subscription on the client side.
To be specific, I have subscribed a collection on the client side, however when I try to refer to it the browser console reported the error:
Exception in template helper: ReferenceError: Chatbox is not defined
Here's the structure of my code:
imports/api/chatbox/chatboxes.js
// define the collection
export const Chatbox = new Mongo.Collection("chatbox");
imports/api/chatbox/server/publication.js - to be imported in server/main.js
import { Meteor } from "meteor/meteor";
import { Chatbox } from "../chatboxes";
Meteor.publish("chatbox", function(parameter) {
return Chatbox.find(parameter.find, parameter.options);
});
imports/ui/chatbox/chatbox.js - page template to be rendered as content upon routing
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import './chatbox.html';
import './chatbox.css';
Template.chatbox.onCreated(function bodyOnCreated() {
this.state = new ReactiveDict();
// create subscription query
var parameters = {
find: {
// query selectors
permission: "1001",
},
options: {
// query options
}
};
Meteor.subscribe("chatbox", parameters);
});
Template.chatbox.helpers({
canAddMore() {
// Chatbox collection direct access from client
return Chatbox.find().count() < 3;
},
});
I'd appreciate if you can help me with this issue. Thanks all for taking your time reading my question!
Regards

You need to import Chatbox in imports/ui/chatbox/chatbox.js:
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { Chatbox } from "../chatboxes"; // correct this path
It's undefined right now because it hasn't been imported.

Related

SvelteKit With MongoDB ReferenceError: global is not defined

I'm trying to setup MongoDB connection library function. I know this function is solid, its used in a whole lot of places (search for Global is used here to maintain a cached connection across hot reloads) and you'll find a whole lot of uses including next.js releases. Note, the purpose of global storage for the database connection is to reduce the overall # of db connections in use at any one time.
What I'm not understanding is the error I'm getting when I import this library via import { connectToDatabase } from '$lib/database';
database.js
// https://github.com/mongodb-developer/mongodb-next-todo/blob/main/util/mongodb.js
import { ENV_OBJ } from "$lib/env";
import { MongoClient } from "mongodb";
const uri = ENV_OBJ.MONGODB_URI;
if (!uri) {
throw new Error("Please define the Mongodb uri environment variable inside .env");
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongo
if (!cached) {
cached = global.mongo = { conn: null, promise: null }
}
export const connectToDatabase = async() => {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const options = {
useNewUrlParser: true,
useUnifiedTopology: true
};
cached.promise = MongoClient.connect(MONGODB_URI, opts).then((client) => {
return {
client,
db: client.db(MONGODB_DB),
}
})
}
cached.conn = await cached.promise;
return cached.conn;
}
The errors:
global is not defined
ReferenceError: global is not defined
at node_modules/mongodb/lib/promise_provider.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:548:25)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/utils.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:6524:30)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/cursor/abstract_cursor.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:10873:19)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/index.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:25281:29)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:25616:23
Note, I do see a file in my generated minimal sveltekit repo called global.d.ts I'm not sure of its purpose. It contains only:
/// <reference types="#sveltejs/kit" />
Any ideas on what's causing the error?
Reference: "#sveltejs/kit": "version": "1.0.0-next.118",
Edit: After spending a whole lot of time on this issue, the global not defined error seems to come from import { MongoClient } from "mongodb"; If I add appropriate console.logs, I can see that the MongoClient function works fine on the server, but then I get the global error on the client. The server indicates no errors at all.
So it turns out I was calling import { connectToDatabase } from '$lib/database' not in a .js helper file or api style (.js) endpoints. I was attempting to use that import and make a database call directly from the <script> portion of a xxx.svelte file.
Definite no go. That generates an immediate global not defined error.

I can't access an object's properties

The next lines work fine and I can see the whole object in the console log:
Meteor.subscribe('projects')
var oneProject = Projects.findOne(key1);
console.log(oneProject)
In the console, I can see the oneProject's properties, even the name property.
Now with the following lines, the result is an error:
Meteor.subscribe('projects')
var oneProject = Projects.findOne(key1);
console.log(oneProject.name)
The error is: "Cannot read property 'name' of undefined".
This is the whole code:
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
import { Projects } from '/imports/api/projects.js';
import ProjectFormUpdate from './ProjectFormUpdate.jsx';
export default ProjectFormUpdateContainer = withTracker(({ key1 }) => {
Meteor.subscribe('projects')
var oneProject = Projects.findOne(key1);
console.log(oneProject.name)
return {
oneProject:oneProject,
};
})(ProjectFormUpdate);
A subscription in Meteor is asynchronous. This means the data is not always immediately available.
Tracker.autorun(() => {
const sub = Meteor.subscribe('projects');
if (sub.ready()){
const oneProject = Projects.findOne(key1);
console.log(oneProject.name);
}
});
will not try to find the project until the subscription is ready.

Meteor + React: Can't get data from mongoDB Collection

I have a Meteor Application that I'm developing with React. I still have the autopublish package in my project (autopublish#1.0.7).
Here is my relevant code:
MainMenu.jsx
import React, { Component, PropTypes } from 'react'
import { Meteor } from 'meteor/meteor'
import { FlowRouter } from 'meteor/kadira:flow-router'
import { createContainer } from 'meteor/react-meteor-data'
import { ChatRooms } from '/imports/api/chatrooms.js'
export class MainMenu extends Component {
render() {
console.log(this.props.chatrooms)
return (
{/* Render stuff here is not part of the scope of this question */}
)
}
}
MainMenu.PropTypes = {
chatrooms: PropTypes.array.isRequired
}
export default createContainer(() => {
return {
chatrooms: ChatRooms.find({}).fetch()
}
}, MainMenu)
chatrooms.js
import { Mongo } from 'meteor/mongo'
export const ChatRooms = new Mongo.Collection('chatrooms')
The console.log(this.props.chatrooms) in the MainMenu Component always returns an empty array ([]).
There are definitely items in the Mongo Database because when I run the meteor mongo command in my console and type db.chatrooms.find({}); it returns the 3 items that I've inserted to test this all.
Anyone have any idea what I may be doing wrong here? Help would be much appreciated!
I've figured it out. I left out a crucial step of this whole process.
In my /server/main.js file I needed to add the following line which fixed everything:
import '../imports/api/chatrooms.js

What's the equivalent of Angular Service in VueJS?

I want to put all my functions that talk to the server and fetch data into a single reusable file in VueJS.
Plugins don't seem to be the best alternative. Template less components..?
In total there are 4 ways:
Stateless service: then you should use mixins
Stateful service: use Vuex
Export service and import from a vue code
any javascript global object
I am using axios as HTTP client for making api calls, I have created a gateways folder in my src folder and I have put files for each backend, creating axios instances, like following
myApi.js
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:3000/api/v1',
timeout: 5000,
headers: {
'X-Auth-Token': 'f2b6637ddf355a476918940289c0be016a4fe99e3b69c83d',
'Content-Type': 'application/json'
}
})
Now in your component, You can have a function which will fetch data from the api like following:
methods: {
getProducts () {
myApi.get('products?id=' + prodId).then(response => this.product = response.data)
}
}
As I assume you want to re-use this method in multiple components, you can use mixins of vue.js:
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.
So you can add a method in mixin and it will be available in all the components, where mixin will be mixed. See following example:
// define a mixin object
var myMixin = {
methods: {
getProducts () {
myApi.get('products?id=' + prodId).then(response => this.product = response.data)
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
// alternate way to have a mixin while initialising
new Vue({
mixins: [myMixin],
created: function () {
console.log('other code')
}
})
I'm using Vue Resource mostly.
1.I create new file where I do connection to API endpoint using Vue.http.xxx.So let's say we have endpoint that output the posts.Create new directory in your project, I call it services, and then create file called PostsService.js - content looks like this:
import Vue from 'vue'
export default {
get() {
return Vue.http.get('/api/posts)
}
}
Then I go to component where I want use this service, and import it
import PostsService from '../services/PostsService'
export default {
data() {
return {
items: []
}
},
created() {
this.fetchPosts()
},
methods: {
fetchPosts() {
return PostsService.get()
.then(response => {
this.items = response.data
})
}
}
}
For more info about this approach, feel free to check my repo on GitHub https://github.com/bedakb/vuewp/tree/master/public/app/themes/vuewp/app
I suggest creating an API Provider that you can access from anywhere in your app.
Simply create a src/utils folder and inside of it a file called api.js.
In it, export your wrapper that knows how to communicate with your API as an object or a ES6 static class (I prefer how the latter looks and works if you're not afraid of classes). This provider can use any HTTP request library that you like and you can easily swap it later by changing a single file (this one) instead of hunting down the whole codebase. Here's an example of using axios, assuming we have a REST API available at api.example.com/v1 that uses SSL:
import axios from 'axios'
import { isProduction, env } from '#/utils/env'
const http = null // not possible to create a private property in JavaScript, so we move it outside of the class, so that it's only accessible within this module
class APIProvider {
constructor ({ url }) {
http = axios.create({
baseURL: url,
headers: { 'Content-Type': 'application/json' }
})
}
login (token) {
http.defaults.headers.common.Authorization = `Bearer ${token}`
}
logout () {
http.defaults.headers.common.Authorization = ''
}
// REST Methods
find ({ resource, query }) {
return http.get(resource, {
params: query
})
}
get ({ resource, id, query }) {
return http.get(`${resource}/${id}`, {
params: query
})
}
create ({ resource, data, query }) {
return http.post(resource, data, {
params: query
})
}
update ({ resource, id, data, query }) {
return http.patch(`${resource}/${id}`, data, {
params: query
})
}
destroy ({ resource, id }) {
return http.delete(`${resource}/${id}`)
}
}
export default new APIProvider({
url: env('API_URL') // We assume 'https://api.example.com/v1' is set as the env variable
})
Next, in your main.js file or wherever else you bootstrap the Vue app, do the following:
import api from '#/src/utils/api'
Vue.$api = api
Object.defineProperty(Vue.prototype, '$api', {
get () {
return api
}
})
Now you can access it anywhere in your Vue app as well as anywhere you import Vue itself:
<template>
<div class="my-component">My Component</div
</template>
<script>
export default {
name: 'MyComponent',
data () {
return {
data: []
}
},
async created () {
const response = await this.$api.find({ resource: 'tasks', query: { page: 2 } })
this.data = response.data
}
}
</script>
or:
// actions.js from Vuex
import Vue from 'vue'
export async function fetchTasks ({ commit }) {
const response = await Vue.$api.find({ resource: 'tasks', query: { page: 2 } })
commit('SAVE_TASKS', response.data)
return response
}
Hope this helps.
I think for your simple question the answer could be any ES6 module containing functions (equivalent to methods in class in ANgular) and directly importing them in components using ES6 imports and exports. There are no such services that could be injected in components.
You can make your own service where you can place all your HTTP server calls and then import that to the components where you want to use them.
Best is to make use of Vuex for complex state management applications because in Vuex you are able to handle all async calls via actions which always run asynchronously and then commit the mutation once you have the result.Mutation will directly interact with the state and will update it in an immutable manner (which is preferred). This is stateful approach.
There are other approaches as well. But these are the ones which I follow in my code.

undefined is not a function (evaluating '_reactNativeMeteor2.default.collection("messages").find().fetch()')

In my Meteor app I have a collection definition like this:
this.collections.Messages = new Mongo.Collection("messages");
Now I try to connect to it from a react native meteor like this:
import React, { Component } from 'react';
import Meteor, { createContainer } from 'react-native-meteor';
import MessageListComponent from '../routes/messageList';
export default MessageListContainer = createContainer(() => {
const messagesHandle = Meteor.subscribe('userMessage');
const loading = !messagesHandle.ready();
const messages = Meteor.collection("messages").find().fetch();
return {
loading,
messages
};
}, MessageListComponent);
But it's return below red error message on device:
undefined is not a function (evaluating '_reactNativeMeteor2.default.collection("messages").find().fetch()')
What is the problem guys?
Try eliminating the fetch() from your messages const:
const messages = Meteor.collection('messages').find();
The fetch converts the cursor into an array, and probably isn't necessary here. Also, this line is the only one where you have double quotes, but I'm not sure that that is relevant.