This project is a part of Front-End Web Developer course.
Project contains application for searching a car wash facility near your location. Project is a final graduation task, focused on proper usage of React components, state management, external API and more.
- (easy) use online version hosted on GitHub pages;
- (preferable) download the source code archive;
- (advanced) clone a git subtree from GitHub;
- make sure NodeJS installed on your machine by running
node -v. - install Yarn package manager via
npm i yarn --dev - run
yarnto install dependencies - project relies on mono-repository principle powered by Yarn Workspaces
Project assembly is powered via Webpack, so any related configuration flags will apply. For example, to change a serving port you could provide --port $PORT$ flag. You can find webpack config file here. The full list of options is defined in Webpack CLI documentation
- run
yarn buildto make production build - run
yarn webpackto make development build
By default, you should find output files in dist directory.
No server required to run the project, you can run application by opening index.html in your browser. However, the service work will not work in that case, since it doesn't support file:// protocol.
To serve project files in development mode with HMR (Hot Module Replacement) support, run yarn serve.
By default, you should able to access the app being served on localhost:8080
Project build process is powered by custom yet flexible webpack orchestration including TypeScript support. You can find all of the configuration options and bootstrapping in webpack-configurer.
The main purpose of the app is to help you to find a place where to wash a car. For that reasons, the Google Places API is being used. Every time you move the map, the request is sent to search for nearby places with car_wash type. That means - there are no pre-defined markers on a map, and all of the content is served dynamically depending on your needs. For better car wash facility choice, the Foursquare API provides possibility to display how much people liked the place.
Works on both mobile and desktop devices
At the left side, you can find toggle-able navbar where you can filter nearby places by different search criteria, matching regular expression against: name, city, street, country. Hovering mouse over the item in a list, will highlight the marker on a map and vice versa, but only if it's not part of marker cluster. By clicking on an item, the marker info window appears.
When marker info window opens, it triggers request fetching place details, which include precise address, phone number, website and ratings. As an extra option, it also tries to find same place via Foursquare Places API and if it's there, fetch number of people likes the place.
Application use a service worker to manage how resources are being delivered for better user experience. The strategy may vary on a target host, but it's easily configurable via custom service worker router. For your disposal there are: cache-only, cache-first, network-only, network-first, fastest strategies available. Combining those, made it possible to deliver all of the data, including Google Maps, in an offline mode. For places requests from Google and Foursquare, the IndexedDB powered by Dexie being used out of the service worker.
Project is fully written in TypeScript (yes, including the service worker), which is a powerhouse combining with React. TypeScript makes easy tracking of the type relations, allows to use latest JS features and defines API contracts for better work in a team and ease of code navigation.
Some of the techniques were applied to optimize performance. Having a lot of markers on map was producing heavy slow-downs on every state change (like hovering marker or item in a list) reducing the app FPS < 1.
For that reason some optimizations were made:
- places filter shows only first 20 best matches, but not a whole list
- markers have been merged in a clusters to reduces number of draw calls
- an application state was moved from the root application component, to shared isolated store services, resulting in simple, yet powerful state management system, which relies on principle of "thinking in React". Refer to [State Management](#State Management) section for more info.
Components state is split up between different stores which are powered by React-like setState approach of state management via store. The working principles are close to recently discovered Unstated state manager - state management with react in mind. Such technique (aka Flux) adds complexity by introducing Component <> Store relation (could be solved by, IoC but it's out of this project scope) and forces to use state-full components. But the benefits are great - it greatly improves performance by avoiding data calculations for the whole application tree and props propagation through a parent-child relations on every state change. Having a domain-specific stores (opposite of Redux global store) makes:
- components (views) being isolated from the application context, but only defining what type of external state they need;
- application (controller) bound to actual logic implementation, but not just props propagation;
- stores (model) type-safe and quicker since there are less data to check for modifications.
You could be surprised by looking at google-maps package since it just re-exporting existing namespace, but it's done specially to avoid referencing Google Maps object before the API script is loaded. Thankfully to TypeScript type erasure, all of those imports are removed after build. Consider example:
import {Geocoder, GeocoderRequest} from "$google/maps";
function geocode(request: GeocoderRequest) {
const geocoder = new google.maps.Geocoder();
geocoder.geocode(request: GeocoderRequest)
...
}
where imports are used purely as a types, but actual object instance being created via google.maps namespace.
Project has strictly education purposes and will no accept any pull requests.
This project is licensed under the MIT License - see the LICENSE file for details