||2 days ago|
|public||2 weeks ago|
|src||2 days ago|
|.eslint-config-preact-nojest.cjs||2 weeks ago|
|.eslintrc.cjs||2 weeks ago|
|.gitignore||2 weeks ago|
|.prettierrc||2 weeks ago|
|README.md||1 week ago|
|challenge.png||2 weeks ago|
|index.html||2 weeks ago|
|package-lock.json||1 week ago|
|package.json||1 week ago|
|tsconfig.json||2 weeks ago|
|vite.config.ts||2 weeks ago|
Senior Web Developer
Thank you for your application! We like what we have seen so far from you and want to take the next step by inviting you to take on a small challenge.
Develop a React app that enables you to plan your cross-country run and download it as a GPX file.
Cross-country runners are not bound to the streets. Your users can plan their favorite route across fields and hills by just placing markers as waypoints on the map. For detailed planning, the same waypoints show up as a list, where users can delete and rearrange them until the route is perfect and ready to download. The user interface should be close to the design to the left.
We'd expect a senior web developer to manage to complete this challenge in a few hours. Impress us with your coding skills by not using 3rd-party React components (such as react-leaflet, react-dnd, etc.). At least the map and the list should be separate React components.
Send us the sources as a ZIP file, a link to the hosted solution ready to use, and let us know what you have learned while developing.
As komoot website says, "[challenge] definitely is a great chance to learn something new", so I decided to try
preact instead of
after hearing about it from Xe Iaso (who sadly stopped development of xer Xeact).
It was definitely an experience setting up a project with
preact (their defaults regarding linting and typechecking are not exactly sensible),
but also the resulting bundle is just 65KB, gzipped (42 of which is Leaflet).
And all the actual code should be compatible with Facebook's
react as well.
Since using third-party react components is disallowed by this challenge, I only use the following third-party libraries:
Leaflet(non-react package), to avoid having to integrate with OSM myself, manually, which would take unreasonable amount of time and also not something I would look forward for);
nanoid, to generate random IDs to identify waypoints;
Sortable.js(non-react package), because there is no way in hell I'm going to try to implement drag and drop myself in a cross-platform way; I have life;
@dwayneparton/geojson-to-gpx, an unpopular but tiny (2KB) package to create gpx files (as opposed to much more popular
gpx-builderwhich is 584KB with all its dependencies);
file-saver, because implementing my own in a cross-platform way would be a waste of time;
haversine-distanceto compute distances between points (and total route length).
How to run
npm run dev.
For release build that does not require any interactivity on server,
npm run start-release
(which lints and typechecks and tests everything, builds the project into static bundles in
dist/, and serves static files from
While the original task didn't mention it, it seemed like a good idea to make markers draggable (in order to be able to change the position of an existing marker).
Leaflet only supports dragging natively for ordinary markers, and in order to comply with the appearance on the screenshot above,
I had to use circle markers which do not support dragging natively.
It could be implemented, but to do it in a cross-platform way and to cover all edge cases would require a lot of effort and time I didn't have,
and would make this solution much less readable.
The original task also didn't mention how are people going to orient themselves in the marker list. Considering that it should have drag&drop, it is an especially important matter, since they are all just called "Waypoint #number", and presumably reordering should renumber all waypoints. So if an user drags "Waypoint 4" in place of "Waypoint 1", the left section will not change visually, only the route on the map will change, and this is very very confusing. So I also added editable long marker labels (first four letters of unique marker key by default) in order to make user experience better.
I also added "move up" and "move down" buttons for every marker, because for some people it can be much easier to use compared to drag&drop.
And I also compute and display total route length (without considering the altitude changes), I think this might be convenient.
The final design only remotely resembles the screenshot above, because I'm not a designer and I spent too much time on actually implementing the logic already.
There are tests for non-
preact code (collection-related methods and main reducer, with 100% code coverage),
but not for any react components or the app as a whole,
because dealing with map GUI automatically is difficult enough, and I didn't have nearly enough time.
Components in this solution interact with each other via props, an alternative would be to use react contexts or redux maybe, but the current implementation is simple enough and it works.
Tested manually in latest Firefox, not tested in Chromium or Chromium-based browsers.
What I have learned
- That if W3C / WhatWG were not controlled by corporations, we would probably have native standard widespread easy-to-use APIs for sortable lists and file saving, but instead we have a lot of features for advertising and other stuff that make it impossible to implement a new browser (probably on purpose) (I didn't learn the second part, but I was unpleasantly surprised at the state of affairs with drag&drop and with file saving).
- That the most popular library for building gpx files is also the most bloated;
- Also this was the most
useEffects I've written in a while (in order to integrate with
- How to (relatively easy) add a map to my application (thanks to
- How to configure application created from
preacttemplate to use strict eslint checking and strict typechecking (out of the box it has no linting and no typechecking at all) which requires, among other things, adding a bunch of explicit babel dev dependencies, disabling jest linting and using a customized eslint-config-preact for that.