Building CampaignHawk: Mapbox.js and Meteor Data (Part 12)
Now it’s time to add data layers on top of our map. We want to do this programmatically so we can add and remove these layers when a user checks or un-checks a box in a popout from the data layer icon in the sidenav. Fortunately, Mapbox makes this pretty easy.
The general structure of the function we’re going to use looks like the code below, with the relevant bits of information filled in:
var <layer name> = L.mapbox.featureLayer().addTo(map);
<layer name>.setGeoJSON(<array of data>);
We have a couple options for our data. We can use the data.geojson file that lives in our public directory or we can just use the data in our VoterDataGeoJSON collection. We’re going to use the data straight from the collection, but I’ll also show you how you’d do it if you wanted to pull it from the data.geojson file.
To use the data from the data.geojson file, you’ll need to make an HTTP request. Within the Mapbox.loaded conditional in Map.jsx, we need to make a GET request to retrieve the file, then create the layer, then set the GeoJSON to the layer. Keep in mind that the GeoJSON needs to be in an array or it will not work.
HTTP.get(Meteor.absoluteUrl("data.geojson"), function(err, res) {
var voterLayer = L.mapbox.featureLayer().addTo(map);
voterLayer.setGeoJSON([JSON.parse(res.content)]);
});
But we’re going to use the data in our VoterDataGeoJSON collection. This is where we need to start thinking about how Meteor data works with React. We’re going to do a few things that are a general best practice and a few things that are relatively new to React and Meteor.
One thing we’re definitely going to do is wrap our components in a higher order component so we can keep our views pure and manage our data separately. In short, we want our component structure to look more like this:
<MainView> <Data
<SubView1 />
/> <Data
<SubView2 />
/></MainView>
The other thing we’re going to do is turn our data components into composable components, which will manage our subscriptions and fetch our data in a clean and easily understood manner. You can read more about composable components here.
So now that we’re dealing with data, we need to remove two default packages:
$ meteor remove autopublish insecure
This means we need to set up our publication on our server. Within Server.jsx we need to add:
Meteor.publish('geojson', function() {
return VoterDataGeoJSON.find();
})
So now that we have a publication, we need to subscribe to that publication on the client. This is where our composable components come in handy. First we need to create a MeteorData wrapper component. I’ll stick it in App.jsx since it’s a global wrapper and that file is pretty empty.
What we’re doing in this component is taking advantage of the ReactMeteorData mixin that Meteor gives us. Although mixins are on the way out, this is a perfectly legitimate use of a mixin, because we’re using it to compose a higher-order component which neatly separates our concerns.
We’re also passing subscribe(), fetch(), and render() as props, as well as the data and the loading state. Again, if you want to read more about composable components, you should read this article.
MeteorData = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
const sub = this.props.subscribe()
const data = this.props.fetch()
data.loading = !sub.ready()
return data;
},
render() {
return this.props.render(this.data)
}
})
So now that we’re publishing our data and we have a way to manage it in a composable component, we need to prepare our data layers. I haven’t decided on the best way to handle this once we have multiple data layers, but for we’ll just do something simple.
First let’s extract the content of our render function in our Map component and put it in a new component cleverly named MapChild. We then need to pass in all the functions and state variables that the child components need.
<MapChild
showModalState={this.state.showModalState}
hideModal={this.hideModal}
showModal={this.showModal} />
Note that I had to change the name of the state variable showModal to showModalState. I’m going to skip the part where I change all of them. We also need to change the functions in MapChild to this.props.____ because the reference is now coming from props.
Now we wrap our MapChild component in our MeteorData composable component to handle our subscription. This might look daunting at first, but it’s actually quite simple. Just follow the props being passed:
<MeteorData
subscribe = { () => {
return Meteor.subscribe('geojson') }}
fetch = { () => {
return {data: VoterDataGeoJSON.find().fetch() } }}
render = { ({loading, data}) => {
return <MapChild
showModalState={this.state.showModalState}
hideModal={this.hideModal}
showModal={this.showModal}
loading={loading}
data={data}
/> }
}
/>
We’re subscribing to geojson, which we are publishing in Server.jsx, fetching the data from that subscription, and passing in the loading state and the data to the child component.
Now let’s implement a function to show that our data actually works even though we will eventually call this function from somewhere else. Within the render function of MapChild, we’re going to add the following to add a data layer as soon as the data loads. This is not how we’re going to implement it in the long-run, but we want to get something to render before we move forward.
if (!this.props.loading) {
var voterLayer = L.mapbox.featureLayer().addTo(map);
voterLayer.setGeoJSON(this.props.data);
}
Also note that we need to change map to a global variable the top of Map.jsx by removing the “var” in front of it. You should end up with a map like the one below.
Unfortunately, we’re passing in too much data. It might seem like your browser is frozen when you refresh, but that’s only because it’s trying really hard to render all these datapoints. We’ll optimize this later.
One last thing, I noticed that the styling of our box-shadows take in a grey color. That’s not ideal, especially once we start stacking items on top of each other. I went ahead and changed all my box shadows to black with 50% opacity:
box-shadow: 0px 0px 15px 0px rgba(0,0,0,0.5);
Next Steps
Now we need to do some more work on the frontend to allow a user to programmatically turn data layers on and off. Once we do that, we need to optimize the data so it doesn’t almost crash our browser every time it renders, and make a few functions to show different data in different ways.
Sam Corcos is the lead developer and co-founder of Sightline Maps, the most intuitive platform for 3D printing topographical maps, as well as LearnPhoenix.io, an advanced tutorial site for building scaleable production apps with Phoenix and React.
Cumulative Time
These last few articles have been seriously time intensive… I’ve spent more than two-thirds of my time writing rather than coding. That’s either because these articles are getting more in depth, or Mapbox and Meteor are super easy to use. Or both?
Additional
- Building CampaignHawk: An open-source election canvassing app with Meteor and React (Part 1)
- File Structure and Packages (Part 2)
- Styling the Sidenav (Part 3)
- Styling Tooltips (Part 4)
- Connecting Tooltips with React (Part 5)
- Making Modals (Part 6)
- Populating Modals (Part 7)
- Connecting Modals with React (Part 8)
- Mapbox and Data (Part 9)
- Thinking about Data (Part 10)
- Mapbox and GeoJSON (Part 11)
- Mapbox.js and Meteor Data (Part 12)
- Popouts and Radio Buttons (Part 13)
- Radio Button Styling (Part 14)
- Triggering Functions with Radio Buttons (Part 15)
- Spacial Analysis Overview (Part 16)
- Clustering with Leaflet Markercluster (Part 17)
- Making a Precinct Data Layer (Part 18)
- Scaling Colors and Values with D3 (Part 19)
- Styling the Precinct Data Layer (Part 20)
- Toggling Data Layers (Part 21)
- Voter Filter Data Layer (Part 22)