Styling using Twitter Bootstrap and Bug fixes
Although, everything’s setup now, the UI looks quite unstyled. We have already added some Bootstrap classes in EmployeeDetail component but haven’t installed Bootstrap library to affect the styling. Let’s install Twitter Bootstrap and beautify the webapp. As usual, go to your terminal or command prompt and run this command:
$ meteor add twbs:bootstrap@3.3.6
This will install a specific version of Twitter Bootstrap library. We install a specific version to avoid any conflict between this example and other users styling due to future updates to the Bootstrap library. Installing this library will update the styling of the web page since we already added the appropriate Bootstrap CSS classes above (like list-group and list-group-item). Installation of the library makes the appropriate CSS styles available as part of the project.
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);
This will install a specific version of Twitter Bootstrap library. We install a specific version to avoid any conflict between this example and other users styling due to future updates to the Bootstrap library. Installing this library will update the styling of the web page since we already added the appropriate Bootstrap CSS classes above (like list-group and list-group-item). Installation of the library makes the appropriate CSS styles available as part of the project.
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
Flexbox Styling and Buttons
As of now, the html card which displays the detail of each employee seems to have extra width. We should also try to style the pages such that they are mobile as well as tablet responsive. We can do it in our page easily using flex-box styling.
When working with Meteor, the css files automatically get executed. You don’t need to include them manually. Just add these lines of code in your main.css to see the change:
client/main.css
body {padding: 10px;font-family: sans-serif;}.employee-list {display: flex;flex-wrap: wrap;justify-content: space-around;}.thumbnail {width: 300px;}
Everything is set now. You can see how nicely flex-box styling works when you change your screen width. We are almost done with the frontend except for adding a Load more button and figuring out how to load more employee rows when that button is clicked. To do so, we are going to use an event handler in React. To add an event handler, we will need the button tag first. Here is the code after adding the button code and event handler in it:
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><h2 className="text text-primary text-center"> My Employees </h2><div className="employee-list">{props.employees.map((employee) => <EmployeeDetail key={employee._id} employee={employee} />)}</div><button onClick= {() => console.log("Button Clicked")}className="btn btn-primary">Load more</button></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 look at the button tag, we added an onClick listener which executes the function it is initialized with. Here, we can check if it actually works by clicking on the Load more button in the browser and getting the “Button Clicked” message printed in the console.
Let’s continue with the next section where we will modify the onClick handler to add more data on the screen.
Updating Subscriptions
We now have a place in our application where we can listen to the button being clicked. We want to increase the number of records when we click the Load more button. To increase the number of records, we need to update our subscription. Right now, we have subscribed to the publication employees. We can however, pass additional arguments along with the subscription. This is where we will pass the number of records we need to display and dynamically set the publication according to passed parameter value. We will understand how this flow works once we try this experiment.
So, in our employees_list.js file, let’s pass a parameter which states the number of records needed to be fetched. We can do it 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';import EmployeeDetail from './employees_detail';const PAGE_VALUE = 20;const EmployeeList = (props) => {//props.employees --> An array of employeesreturn(<div><h2 className="text text-primary text-center"> My Employees </h2><div className="employee-list">{props.employees.map((employee) => <EmployeeDetail key={employee._id} employee={employee} />)}</div><button onClick= { () => Meteor.subscribe('employees', 40) }className="btn btn-primary">Load more</button></div>);};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees', PAGE_VALUE);//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
This is how we pass the parameter. We have passed PAGE_VALUE which is defined as 20. We have also added subscribe function as onClick button event. Whenever the button is clicked, Meteor receives 40 as subscription parameter and gives access to 40 records instead of 20. Since the subscription is updated, the withTracker function re-renders the component with updated data and we see 40 records on the screen. Before doing anything else, let’s first update the publication code to receive the parameter as follows:
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(page_value){ //receives the argument page_valuereturn Employees.find({}, {limit: page_value});});});
Publish function now takes an argument page_value and returns page_value objects.
You can test the click button now. Open the browser and we see that initially 20 objects are loaded. When you click on click more button, we get 20 more records on the screen. Also, if you click again, nothing happens because the subscription parameter is fixed as 40. We need to fix this and our project will be finished.
Refactoring code from Function based component to Class based component
We now know that we need to increase our subscription value whenever the “Load More” button is clicked. We just need to make sure that every successful click updates the subscription value. Also note that we are not recreating the subscription on onClick handler: we are just updating the subscription which means we will not toss away initial data but just add new data for each button click.
So, we need to somehow increase the subscription value. To do so, we need to do a bit of refactoring of code. Till now, we have EmpoyeeList as a functional component and if you are familiar with React.js, you know that function based component is just to use some props and display the contents using HTML. We can’t really do any dynamic tasks like changing the state or value of some variable on clicking a button. In short, function based components have no persistence of data. We need to switch to class based components to perform dynamic activities. So, we need to import Component from React and use it to create class based component as follows:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React, {Component} from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data';import EmployeeDetail from './employees_detail';const PAGE_VALUE = 20;class EmployeeList extends Component{render(){//this.props.employees => an array of employee objects//console.log(this.props.employees);return(<div><h2 className="text text-primary text-center"> My Employees </h2><div className="employee-list">{this.props.employees.map((employee) =><EmployeeDetail key={employee._id} employee={employee} />)}</div><button onClick= { () => Meteor.subscribe('employees', 40) }className="btn btn-primary">Load more</button></div>);};};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees', PAGE_VALUE);//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
If you observe closely, we have made one more change, instead of props.employees, we have used this.props.employees to refer to employees object. This is because we had direct access to props as parameters in function based component. Thus, this keyword was not required. But in case of class based component, all the metadata including props are stored in this keyword. So, we need this.props.employees to get the employees object.
Finalizing the Project
In the previous section, we refactored the code to class based component. We did so because we wanted to keep track of some variables within it. We wanted to know how many times user clicked the button. Also, if you recall from earlier part of this project, Minimongo syncs with the actual database whenever there is any change in subscribed data and whenever there is any change in Minimongo’s collection, withTracker automatically re-renders the component. So, we just need to figure out logic to change the subscription when Load more button is clicked. As the subscription will change, component will automatically be re-rendered by withTracker function.
Let’s modularize the code by creating a function which is triggered whenever the button is clicked as:
imports/ui/components/employees_list.js
import { Meteor } from 'meteor/meteor';import React, {Component} from 'react';import { Employees } from '../../api/employees';import { withTracker } from 'meteor/react-meteor-data';import EmployeeDetail from './employees_detail';const PAGE_VALUE = 20;class EmployeeList extends Component{constructor(props){super(props);this.page = 1}onClickPage(){this.page+=1;Meteor.subscribe('employees', PAGE_VALUE * this.page);}render(){//this.props.employees => an array of employee objects//console.log(this.props.employees);return(<div><h2 className="text text-primary text-center"> My Employees </h2><div className="employee-list">{this.props.employees.map((employee) =><EmployeeDetail key={employee._id} employee={employee} />)}</div><button onClick= { this.onClickPage.bind(this) }className="btn btn-primary">Load more</button></div>);};};export default withTracker(()=>{//set up the subscriptionMeteor.subscribe('employees', PAGE_VALUE);//return an object. Whatever is returned will//be sent to EmployeeList as propsreturn {employees: Employees.find({}).fetch()};}) (EmployeeList);
Let’s understand this last section of code. We have created a function called onClickPage for better code modularity and used our logic of multiplying the page number with page value. So, as the button is clicked, total records will get increased by 20. The class constructor initializes the page number with 0 before the component mounts for the first time. If you know about React life cycle methods, you can also use componentDidMount function to initialize the page value.
So, that’s all. Congratulations for successfully creating your first web app. You can find the full source code here: anant90/meteor-react-employees-diary-app. You are now ready to proceed with the next advanced level projects.