November 29, 2015 - 2 comments

A quick job board with React, Meteor and Material UI

I recently wrote a mini-tutorial "How to write a job app in 48 lines of code" - and here it is again, but using React JS: Facebook's javascript love-child. This is slightly longer than 48 lines of code, but mainly because I also integrated a Material UI interface. I actually learned React to be able to use this UI library. React is not strictly necessary with the Meteor framework, as Meteor is reactive out-of-the-box - but I like Google's Material UI style, and it required React, so here we are.

Getting Started

Ok, so as before let's just add our packages to a new Meteor project:

meteor create projectname
meteor add react coffeescript http cosmos:browserify mquandalle:jade meteorhacks:npm

Clearly we need the react package, and we also need the browserify and npm packages for the Material UI library. To pull from Github API we need the http package, and I also added coffeescript and jade - because I prefer them to writing html and javascript.

Main Template

Our main page index.jade is fairly empty - we simply need to add the div which react hooks into - thusly:

head
 title ReactJobs
body
 #App

React will render the html to the #App div.

Startup.jsx

Now let's create our startup javascript file.

Jobs = new Mongo.Collection("jobs");
if (Meteor.isClient) {
 Meteor.startup(function () {
 // injectTapEventPlugin();
 React.render(<App />, document.getElementById("App"));
 });
}

I created a collection to hold the jobs data, and in Clientside Startup we add base React.render method and React will render the html into the #App div we placed.

App.jsx

Now onto our app component. This is the largest file in the whole App, weighing in at 63 lines. I place all components seperately in a components folder inside the client folder: client/components. This is quite verbose, so maybe grab a coffee.

const {
 Paper,
 List,
 ListItem,
 ListDivider,
 Avatar,
 RaisedButton,
 AppBar,
 FlatButton,
 IconButton,
 NavigationClose
} = mui;

These are required to use the Material UI library. They refer to the components we will be using.

const ThemeManager = new mui.Styles.ThemeManager();

And here we attach the mui styles to Thememanager. So far, so good.

App = React.createClass({
 mixins: [ReactMeteorData],

Our app is a React class, and we attach our own methods to it. To use React with MeteorData we need to use their Mixin - which is apparently a temporary thing.

childContextTypes: {
 muiTheme: React.PropTypes.object
},
getChildContext: function() {
 return {
  muiTheme: ThemeManager.getCurrentTheme()
 };
},

These two methods are required to use MUI. I'm not going to go into details about the MUI library, if you want more info about it, you can check out the link at the top of this post.

getMeteorData() {
 return {
  jobs: Jobs.find({}).fetch()
 }
},

Here is our method to fetch the Jobs data we will pull from Github jobs API.

componentDidMount() {
  {this.loadJobs()}
},
loadJobs() {
  loadJobs = Meteor.call("loadGithubJobs");
},

When the component loads for the first time we will fetch the jobs list from Github: this could be written in one method, but I like to keep things seperate, so the second method calls the server-side meteor method that pulls from Github API.

renderJobs() {
 return this.data.jobs.map((job) => {
  return <Job key={job._id} job={job} />;
 });
},

Using the new Ecmascript, or latest Javascript, functions we can quickly map the data to each instance of our Job component (which we will create shortly) and return a list of react/html components.

render() {
 return (
  <div className="wrapper">
  <AppBar title="Github Jobs" />
<div className="container">
  <List subheader="Latest Github Jobs">
   {this.renderJobs()}
  </List>
  </div>
 </div>
 );
 }
});

Finally, we render the main App component. In the midst of this you will notice this.renderJobs() which will return the Material List components with our data filled in each one.

Job Component

Now we need the React Job Component we call in the above script:

const {
FontIcons,
IconButton,
Icons,
List,
ListItem,
ListDivider,
Avatar
} = mui;

I've been a bit lazy and haven't deleted the Material components I have not used. I left them there in case I was to expand this app. But you only need to include the components you want to use.

// Task component - represents a single todo item
Job = React.createClass({
render() {
 return (
  <ListItem
  primaryText={ this.props.job.title }
  leftAvatar={ <Avatar src={ this.props.job.company_logo }/> }
  secondaryText={ this.props.job.location }
  href={this.props.job.company_url}
  rightIcon={ <IconButton iconClassName="muidocs-icon-custom-github"   tooltip="GitHub" /> }
  />
);
}
});

This React class will render our List Item component and fill it with the data from our collection and is available in "props".

Pull in the Github data from the API

On the server, we need the method(s) which will pull the Github jobs data via http calls. I write this in Coffeescript.

Meteor.methods
 loadGithubJobs: ->
 @unblock()
 Meteor.http.call "GET", "https://jobs.github.com/positions.json", (error,result) ->
 if(error)
 console.log error
 if(result)
 Meteor.call "writeJobs", (result.data)
writeJobs: (jobs) ->
 Jobs.remove({})
 Jobs.insert job for job in jobs

Here we have two methods: the first one "gets" the jobs list from the API, and, if successful, calls the second method (writeJobs) to push them into our collection. I actually remove the previous Jobs loaded there as a temporary fix for this demo, so we also have the called data and no "old stuff".

I'm not going into the above methods, they are explained in the previous job app post I referenced at the top of this post which you can look into.

Some other bits required for Material UI

If you want to use MUI you will also have to add a couple of package files. You will need to add the following (for example) to your root package.json file:

{
"material-ui": "0.10.1",
"externalify": "0.1.0"
}

And in your client/lib folder you will need a couple of files for browserify. Your json options in the file app.browserify.options

{
"transforms": {
"externalify": {
"global": true,
"external": {
"react": "React.require"
}}}}

And app.browserify.js

mui = require('material-ui');
injectTapEventPlugin = require('react-tap-event-plugin');

More info about this at React and Meteor.

Ok and that's it. I'll admit, this is early stuff - syntax changes, and the React in Meteor mixin, along with the browserify hacks are patches (in my opinion). But, it's how it works right now. So, anyway, you should have a nice responsive, pretty web app like this running in localhost:

mui-jobboard

And here is a live version

If you have any errors - well, I'm not surprised - this stuff is all pretty new and rough. But if it helps you can clone my repo. A word of warning though: React is still very much in its infancy, and I've already found some compiling errors once packages were updated - you know how touchy these dependencies can be...

You can ping me on twitter @derrybirkett if you really want to.

For now, onward!

Published by: Derry Birkett in Development

Comments

Paul
March 8, 2016 at 2:03 am

Really good! Can you please consider doing another with cjsx so that you are using coffeescript more?

    Derry Birkett
    March 17, 2016 at 6:51 pm

    Actually I was looking into it, because I usually use Coffeescript. Maybe I’ll try it out… if you do paste your link back to me here…

Leave a Reply