This is the capstone project for the Udacity front-end nanodegree. In this project we have created a neighbourhood map of Paris using predominantly React and Google Maps. We have also employed some important libraries to help us build this application, like Redux.
Install dependencies with the command:
npm install
Then simply run the application with the command:
npm start
To build a production release and test the service worker, execute the following:
npm run build
This builds your application and readies it for a production release. You can then test your service worker by running the production application as thus:
serve -s build
.
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── img
│ │ └── eiffel-tower.svg
│ ├── index.html
│ └── manifest.json
└── src
├── API
│ └── WikipediaAPI.js
├── App.css
├── App.js
├── App.test.js
├── assets
│ └── svg
│ └── eiffel-tower.svg
├── index.css
├── index.js
├── logo.svg
├── modules
│ ├── List
│ │ ├── ListContainer.js
│ │ ├── actions.js
│ │ └── reducers
│ │ └── index.js
│ ├── Map
│ │ ├── Map.js
│ │ ├── actions.js
│ │ └── reducers
│ │ └── index.js
│ ├── Place
│ │ └── PlaceContainer.js
│ ├── Places
│ │ ├── PlacesContainer.js
│ │ └── reducers
│ └── Search
│ └── SearchContainer.js
├── serviceWorker.js
└── store.js
All important source code, with the exception of possibly index.html
, are located inside the /src
folder. For this project I have decided to organise my React components
around modules, as opposed to adopting the more conventional and functional structure where there is a components/
folder that groups all components together.
The reason why I adopted this approach is so that I can think of my modules as independent, self-sufficient features of a broader application where all necessary code
is included in the given module folder:
- Component
- Actions
- Reducers (more on this below)
Lastly, this project was initially created with the create-react-app
command.
Initially, one of the trickiest tasks was to load the Google Maps Javascript API. I attempted to use a couple of already packaged libraries to this purpose, but found myself frustrated at not understanding what kind of dark magic was going on behind the scenes, so I embarked on discovering the truth by myself and building my own React Google Maps component.
The Map.js
is responsible for loading the Google Maps Javascript API and make it available to all components that require it. Broadly speaking, we dynamically load the Google Maps
library via the following mechanism:
- append a
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY&v=3">
to the body of the DOM - wait for the script to be fully loaded by subscribing to the
load
event - Upon
load
event triggered, execute a function that creates the map and dispatch a Redux event with the map to force a render on all subscribing component
Moreover, we attempt to gracefully handle the cases where we are unable to load the Map. Currently, we use a MutationObserver
to track changes to the the myMap
element. When Google Maps fails to load (e.g. incorrect API key), the library will insert a div with class gm-err-container
.
If the MutationObserver notifies of any such element added, we therefore proceed to perform a little cleanup and display an error message on the screeen as opposed
to Google's error message and layout. The exerpt below shows how we strive to accoplish this task:
const handleErrorFn = this.onScriptError.bind(this);
const callback = function (mutationsList) {
for (var mutation of mutationsList) {
if (mutation.type === 'childList') {
for (const an of mutation.addedNodes) {
if (an.classList && an.classList.contains("gm-err-container")) {
handleErrorFn();
}
}
}
}
};
const mu = new MutationObserver(callback);
const config = { attributes: false, childList: true, subtree: true };
mu.observe(mapElement, config);
In Addition to being able to update the state of a local component or pass props to children with vanilla React, I felt I need to be able to more easily share global state
across many components, regardless of their hierachy. This need was driven by the fact that the map
object should globally be available to all components that wish to use it.
In order to share common state across many components, I decided to use Redux, which is battle tested with React. In Redux, we define a store
which is the application's source
of truth and enable components to subscribe to certain elements within the store to receive updates whenever the store's state changes. The store itself can only be update under
specific cirmcustances by a reducer, which is a pure function that handles state updates.
As part of this project, I also use a third-party API to fetch information about the places of interest. I specifically decided to use rely on information from Wikipedia as:
- It is well known
- The information is generally up to date
- It contains a lot of entries on a wide range of topics
Granted, Wikipedia may not be the most authoritative source of information out there, but for the purposes of this project it should do really well.
Unfortunately, Wikipedia do not provide a Javascript API we could just download and integrate into our project. I wrote one such client called wikipedia-js a couple of years ago but have not maintained it for many years so I also decided to start from scratch and provide a simple Javascript library to perform the following operations:
- Search pages matching a given
query
- Retrieve summary information about a given page
We first search (1) and then use the first result from the search to obtain summary information about the page (2). The API is very simple to use and is Promise-based. It is currently used to retrieve retrieve summary information about the places of interest and display them in a Google Maps InfoWindow.
Many components in addition to subscribing to specific updates from the Redux store, also maintain internal state via this.state
. This is because some of this state
information should not be available outside a given component and its children, which is why they are not being maintained in the Redux store.
For instance, controlled components like PlaceContainer
maintain information about their marker, info window, and info window content.
This information should not be globally available.
We employ basic css; no framework like SASS or LESS are used in this project. The reason why is because I simply felt the size of the project was too small to warrant the overhead of using a framework.
One thing I realised though is that the styles are directly inserted into the html document by create-react-app, which is not ideal. In the future, I'd like to tackle this issue so that the css styles are imported in the HTML. I can easily do this to be honest but I need to find a way to prevent create-react-app from inlining the css into the HTML document.
As we strive to create a responsive, we are using media queries in some places to accommodate for different screen sizes, such as in the following case for the InfoWindow when selecting a place of interest on the list menu or clicking on one:
@media screen and (max-width: 500px){
.place-info-container{
width: 350px;
max-height: 300px;
}
...
...
}
Moreover, we use a sliding menu on the left to enable the map to occupy the whole screen when the user deems the former unnecessary. We simply use a basic animation to transition slide-in or slide-out the menu depending on the user's itent. A css property is toggled to determining which one of the sliding actions to undertake.
There are many potential improvements that could be made to this project. Some of the following come to my mind:
- modify create-react-app or simply writing own webpack scripts to have more control over build process
- improve performance on the general rending and re-use of map components like markers
- better styling for search bar: it looks very plain right now
- have more control over service worker logic
- use more functional componenents
Overall, this was a fun project where I learned a great deal about React, Redux and the general lifecycle events of such libraries.