Autopublish feature vs Publication and Subscription
It’s time to learn about a very important concept of Meteor referred to as Publication and Subscription. This is required to understand how to send data to the client from the backend server.
First of all, let’s understand the Meteor’s default behavior. By default, Meteor provides all the data in the server side database to the client side. This is the feature that is provided solely for the development purposes. It’s the feature Meteor ships with just to make the development process easier. However, in almost every project, it is required to keep our data secure which means only a subset of data should be provided to the client.
For example, consider you are creating an online banking system. You would not like to provide the details of every account to any user. You would provide a user only with his own account details. By default, Meteor would give access of all the account details to the client, i.e., web browser. However, you want them to have access to only a small subset of data i.e., information about their own account only. So, this process of limiting the amount of data that a client can see is called the publish and subscribe system.
The flow of subscribe and publish is as follows. The React application subscribes for some data which the user wants to view. This subscription is received by the server and if the user is authorised to view the data, the server responds by publishing that data to the client. You will understand this clearly once we implement it practically.
Therefore, in short, there are 2 reasons why you would not like to keep up with the default behavior:
- Getting thousands and lakhs of records at one would start causing performance issue.
- You would not want a user to have access to data he is not authorised to.
To understand the Meteor database accessibility at low-level, we have already talked a bit about minimongo and MongoDB. When the server loads on a browser for the first time, Meteor creates minimongo database and syncs it with the MongoDB database at the server. So, by default, Meteor syncs all the data of MongoDB to minimongo. When it comes to subscribe and publish, Meteor syncs only those data to minimongo which are subscribed from the client. As the subscription changes, the updated data is immediately synced with minimongo.
The default behaviour of providing all the data to the client is provided by a package called autopublish. It is a default Meteor package which publishes all the data in the database to anyone who connects to our application.
So, let’s get rid of it by typing following command on the terminal or command prompt:
$ meteor remove autopublish
This will remove autopublish for good. Now let’s set up publication and subscription to make sure that users are getting only the data they care about.
Setting up Publication
Also, you might have guessed by now that the publication is defined by server and subscription is defined by client. Server defines publication and it’s like here is the data available for the client; If you ask for it, I will give it to you. So, we will start by writing code for publish in server/main.js file. Thereafter, the file looks like this:
server/main.js
//only executed on the serverimport _ from 'lodash';import { Meteor } from 'meteor/meteor';import { Employees } from '../imports/api/employees'import {image, helpers} from 'faker';Meteor.startup(() => {//check if data already exists//count the number of records in databaseconst numberRecords = Employees.find({}).count();//filter options are specified in find, here no filter is requiredconsole.log(numberRecords);if(!numberRecords){//generate the records using faker_.times(5000, ()=>{const {name, email, phone} = helpers.createCard();Employees.insert({name, email, phone,avatar: image.avatar()}); //name, email,... equivalent to name:name, email:email,...});}Meteor.publish('employees', function(){return Employees.find({}, {limit: 20});});});
The publish function here defines the publication. Here, the first parameter employees is the name of the publication. Any subscription call to this publication will be known by this name. When there are multiple publications, they are uniquely identified by their name. The second parameter defines the function which returns the actual data. Here, the publication returns exactly 20 employees to the subscribers. Of course, we will make changes here in the future but this is good to start with as we will initially display 20 employees on the webpage.
Before moving on to the subscription part, there is one more thing we need to set up i.e., container. We will do it in the next section.
Setting up Tracker and Subscription
To create a subscription, we need to figure out where we should write the subscription code. We also need to keep in mind that our subscription might change over time. In almost every case, whenever some data is loaded from the backend through a subscription, we should expect that the subscription might change with time. For example, in our project, initially we will display only 20 records and we will have a load more button. When the user clicks load more, they will get another 20 records in addition to the initial 20 records on the screen. Hence, the subscription changes from 20 records to 40 records and so. Thus, the subscription changes with requirements and when it does, we need the React component to automatically re-render the webpage instead of requiring the user to manually click the refresh button.
In short, we need to re-render the web components when Meteor loads more data for the subscription. Meteor provides a package which has a tracker to track the changes in the collection and pass the updated data to the client. So, we will set up the tracker now and then proceed to the subscription. We will now install one npm package and one Meteor package:
$ meteor npm install --save react-addons-pure-render-mixin
This is a required package for a Meteor library we are going to install next. Once this is installed, install this Meteor package:
$ meteor add react-meteor-data
react-meteor-data is the library which provides us the tracker. Remember, we need it to watch the subscription and whenever it changes, update it with the new data. So, open your employees_list.js file and add the tracker as:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data'const EmployeeList = (props) => {//props.employees --> An array of employeesreturn(<div><div className="employee-list">Employee List</div></div>);};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees');//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
Let’s understand what’s happening here. Inside withTracker’s first argument, we have subscribed to the publication named employees we created in the previous section. It will make 20 employees’ data available to the client (since we added {limit: 20} in server/main.js file, and we have returned all of those 20 records in return statement here. Also note that withTracker takes a second argument as a component and returns a new component with employees included as props. You can see the comment inside the component which tells you how to access the subscribed data. Note the ES6 syntax we need to use for passing the EmployeeList component.
To perform a quick check to see if everything’s correct, write console.log(props.employees); statement just before the return statement inside the React component. You should get 20 employee objects in your browser console. If you observe carefully, you may find two different sets of employees objects returned in web console, the first set being an empty list. The first one was when the React application first loaded up, it set up the subscription and was waiting for data to come from the backend. And when it actually came, since the subscription got updated, withTracker reloads the EmployeeList component. Hence, we got console.log() message again but this time with the updated data (i.e., the 20 employee records).
Scaffolding Employee Detail
Now that we are able to access the data on the frontend, let’s display it on the webpage. First, we will create a boilerplate of a component for displaying the details of each employee. So, create a new file employees_detail.js within the components folder as:
imports/ui/components/employees_detail.js
import React from 'react';const EmployeeDetail = (props) => {return (<div>Employee Detail!</div>);};export default EmployeeDetail;
Let’s call this component for each employee within employees_list.js:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data';import EmployeeDetail from './employees_detail';const EmployeeList = (props) => {//props.employees --> An array of employeesreturn(<div><div className="employee-list">{props.employees.map((employee) => <EmployeeDetail />)}</div></div>);};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees');//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
Here, we have used the JavaScript map function to iterate through each of the record in employees object. The function takes a parameter named employee which represents each employee in employees.
Save the files and go over to your browser to get something like this.
Everything’s alright till now if you can see the same in your browser. Don’t worry about the warning message in the console — we will resolve it soon.
Passing actual Data inside the EmployeeDetail component
By now, we have got a list of employee details rendered on the web app. All we have to do now is make sure that we pass the employee data into the EmployeeDetail component. Remember, whenever we communicate between parent and child components, we do so using props.
We can pass the props from EmployeeList to EmployeeDetail inside the component tag as:
<EmployeeDetail employee={employee} />
Talking about the React.js syntax, the first employee is the identifying name with which the item inside the curly braces (employee that is passed as a parameter inside the arrow function) can be accessed in the EmployeeDetail component.
Finally, after passing employee to the EmployeeDetail component, the code looks like this:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data';import EmployeeDetail from './employees_detail';const EmployeeList = (props) => {//props.employees --> An array of employeesreturn(<div><div className="employee-list">{props.employees.map((employee) => <EmployeeDetail employee={employee} />)}</div></div>);};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees');//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
Since we have passed the data inside EmployeeDetail component, let’s display them on the browser by modifying employees_detail.js as:
imports/ui/components/employees_detail.js
import React from 'react';const EmployeeDetail = (props) => {//props.employee --> access an employee detailreturn (<div className="thumbnail"><img src={props.employee.avatar} /><div className="caption"><h3>{props.employee.name}</h3><ul className="list-group"><li className="list-group-item"> Email: {props.employee.email}</li><li className="list-group-item"> Phone: {props.employee.phone}</li></ul></div></div>);};export default EmployeeDetail;
Switch to your browser to see the changes. You will see the details of 20 employees on the browser along with their photos. Here’s what it looks like:
If you have reached this far and can see a page similar to the one above, you are doing great!
Now, even though the above code does the job, it’s always a good practice to keep your code optimized and clean. We are using props keyword multiple times. Since we have only one props in the component, we can get rid of that keyword. Also, we can take advantage of ES6 syntax to clean the same code as:
imports/ui/components/employees_detail.js
import React from 'react';const EmployeeDetail = ( {employee} ) => { //using ES6 syntax instead of propsconst {name, email, phone,avatar} = employee; //destructuring syntaxreturn (<div className="thumbnail"><img src={avatar} /><div className="caption"><h3>{name}</h3><ul className="list-group"><li className="list-group-item"> Email: {email}</li><li className="list-group-item"> Phone: {phone}</li></ul></div></div>);};export default EmployeeDetail;
We can see how our code looks nicer and cleaner now.
Back in our browser, you might have noticed a warning message in the web console which we mentioned in the sections above. If you read it, it’s talking about some unique “key”. Whenever we see an error or warning about a key property, it means that React is trying to render an array or list of elements where we need to give a unique key property to each element. When we see our employees_list.js, we are rendering a list of employees for the EmployeeDetail and this is what React is complaining about. All we need to do is give a unique identifying key to each EmployeeDetail component. Since we are mapping over a list of employees, each employee has a unique id (called _id) which is automatically created by MongoDB. We can use that id property to serve as the key for EmployeeDetail. We can add it inside the EmployeeDetail tag as
<EmployeeDetail key={employee._id} employee={employee} />
After adding it, our updated file looks like this:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data';import EmployeeDetail from './employees_detail';const EmployeeList = (props) => {//props.employees --> An array of employeesreturn(<div><div className="employee-list">{props.employees.map((employee) => <EmployeeDetail key={employee._id} employee={employee} />)}</div></div>);};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees');//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
If you open your web console, you should see no more warnings or errors. Let’s continue a bit more with the styling. Once, done, we will add the Load more button as the final step.