For my final project for Flatiron's online fullstack programming course, I built an app called Trailista, which pulls a list of trails from Hiking Project Data API by location. You can watch a short demo of my app here or proceed to read as I briefly touch up on the setup and few roadblocks I came across during the process.
This app is definitely a WIP but so far here are the available featues:
- One can search for trails by city and country
- One can login or sign up
The way it works
- My app pulls trails from Hiking Project API endpoint that looks something like this
www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=APP_KEY
The client supplies the values for lat, lon and maxDistance alongside a key that is provided by Hiking Project
The app then takes the user inputs and plugs them into a
fetch
request, and sends it to Hiking Project API for a response back
Sounds pretty straightforward except that the user supplies the app with an address, e.g. a place's name, whereas Hiking Project API endpoint wants the address in the form of GPS coordinates, i.e. the lattitude and the longitude. So I had to find a way to convert the location input to GPS coordinates. After rummaging through Hiking Project' FAQs forum for some time, I found a thread where someone had mentioned using MapBox to carry out, what they apparently call, "forward geo-coding", i.e. the process of converting an address to coordinates (the opposite would be reverse geo-coding). I also looked into Google maps API but apparently they charge? So, I decided to stick with MapBox.
And, boy was this fun? Yes, it involved some hair-pulling episodes but I'm fairly proud of this part the way I got it working. My two functions, the heroes of my app, getCoordinates()
and fetchHikes()
, work in coordination to make this happen.
-
getCoordinates()
takes the user input and sends afetch
request to MapBox API, like so:
getCoordinates= (e) => {
e.preventDefault();
axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${this.state.city}.json?country=${this.state.countryCode}&access_token=APP_KEY`)
.then(resp => {
this.setState({
longitude: resp.data.features[0].geometry.coordinates[0],
latitude: resp.data.features[0].geometry.coordinates[1]
})
...
- The response data is then used to set the values of
longitude
andlatitude
in the state, which is then used to make afetch
request to Hiking Project's API via the dispatch actionfetchHikes()
getCoordinates= (e) => {
...
this.props.fetchHikes(this.state.latitude, this.state.longitude, this.state.maxDistance, this.state.maxResults)
})
}
The Set Up
This article does a great job of guiding one through setting up a React app with Rails API. I had a rough idea of what resources I will be working with so I started with my models and controllers first and then set up (API) routes (in '/config/routes.rb') to which I could send fetch
and/or post
request.
Important!
One thing to look out for is your CORS (Cross Origin Resource Sharing) set up. Since my front-end and back-end were running on different domains, I had to find a way to tell my back-end server that the requests my front-end was sending were not harmful. If you initially set up your Rails API with the command $ rails new myapp --api
, you get a cors.rb
file already set up and you will just have to uncomment the following:
#Gemfile
gem 'rack-cors'
#app/config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Once all this was taken care of, I then moved onto my client side, i.e. the front-end part. Also, before pulling data from Hiking Project, I created some seed data to test how the data is rendered on my front-end.
Routes
I set up my routes in App.js
. One interesting thing I found on the internet was that I could create a helper PrivateRoute
to lock down a route if one is not logged in, like e.g. my '/user' route. This article talks about how one can set up a PrivateRoute
.
Components
I started with presentational components that were mostly stateless and had DOM markups. As I progressed further I ended up adding props, state, connecting to the store and so on, which all led me to think about dividing up my components based on their functionality - containers and presentational. Containers are concerned with how things work, contain logic and the most of the times are stateful. The latter, on the other hand, are just "dumb" components that are concerned with how things look and has no logic or knowledge of state whatsoever.
However, as Dan Abramov points out in this article, this pattern should not be enforced as a hard rule and if there is no necessity. So, you may see that some of my components act as both container and presentational or some of my presentational components have logic inside them and are not purely presentational.
Reducers
Speaking of separation of concerns, instead of having one giant reducer, I decided to have one reducer managing only one independent slice of the state. So, I ended up with three reducers, alertsReducer
, hikesReducer
and userReducer
. All of these reducing functions are then combined into a single reducer, rootReducer
, using combineReducers
helper function provided by redux
. This reducer is what gets passed to createStore
.
Conclusion
I had a lot of fun building this application. And, like with all other projects, this one came with frustration but I've learned that that's what makes the accomplishment at the end so much worthwhile. However, this is not over yet and I still want to keep working on the app, adding some more features like comments, users being able to rate and create hikes and so on. For all I know, sky is the limit and there is always room for improvement. As of now, I'm happy that I'm ready for my final assessment. The journey has just begun!
Top comments (0)