How to use React to build Web Apps
Maps are a powerful tool for visualizing and understanding geographic data but they need specific skills to be designed efficiently.
In this step-by-step guide, we are going to take a deep dive into building a map-based application to show the customer's prices of gas stations around them. We will cover the different key steps of a product, from original proof of concept (POC) to the minimum viable product (MVP)
Articles in the series:
Part I: The proof-of-concept – Build a minimalist demo
Part II: How to use React to build web apps (Static Layout)
Part III: Add Interactivity to your web apps with React
Part IV: Build a back-end with PostgreSQL, FastAPI, and Docker
A bit of context around this article
In this article, we will continue our work on the Gas Station Finder Application that was initiated previously. This time, we will focus on preparing the first version of the web application that can run locally using static datasets. The web application will be built using the React framework and this article will provide as much detail as possible for someone who is new to the framework.
I decided to go with React for this application as it is a compelling framework that only a few data scientists/data analysts are currently using. If you have been ever frustrated by the rigidity of other frameworks, I hope you will find in this article an alternative way to design your web application. You will see that the learning curve might look a bit steep at the beginning, (especially if you are not familiar with HTML/CSS/javascript), but once mastered, having this framework in your toolbox will open up a lot of opportunities for building dynamic, user-friendly and scalable web applications.
Note: The article will not include all the code, only snippets to illustrate key principles, but the full code can be found on my GitHub page.
By the end of the article, we will have a layout of our React Application, and in the next part, we will focus on adding interactivity to it.
Why use React?
There are many tools and frameworks available to build nice and powerful web applications. Some of the most adopted frameworks in the data science ecosystem include Dash or Streamlit. One of the big advantages of those tools is their simplicity and ease of use, which can be very efficient and sufficient for some of your use cases, especially if you are using mainly python.
Nevertheless, it comes with a trade-off on flexibility and customization, and if your application starts to be more complex, the amount of time to develop your features within those frameworks will grow exponentially.
React, on the other hand, is a framework that is more performant and allows for more flexibility. It also encourages the use of a modular and component-based architecture, which is appreciable when having to add new features and do the maintenance of the project. Finally, it is part of the JavaScript ecosystem, which is one of the largest and most popular ecosystems in the world.
Despite its advantages, you might be reluctant in learning the framework, especially if you come from the python world. Not only would you have to learn the framework itself, but also probably some javascript, HTML, and CSS, which can be very time-consuming.
Regarding HTML and CSS, a basic knowledge of those languages is in any case needed even if you try to go deeper into your application (even in dash or streamlit). Javascript might be a bit more challenging, especially if you are not so much familiar with object-oriented and asynchronous programming, but once learned, it will potentially give you access to a more easy and efficient way of developing web apps (as well as sharpening your programming skills)
Setup the Project
To start a project powered by react, you first need to install Node.js on your computer, which is the runtime environment used to run javascript.
Node.js can be downloaded here.
Side note: Things are changing fast. I wrote this article in March 2023 and I am using React 18.2.0. If you read this article years after its publication, it might be a bit outdated. So be careful with what you will read below.
Create a new project
Once installed, we will use npx/npm (roughly the equivalent of pip in python) to set up a react project via the command "create-react-app"
npx create-react-app fuel-station-front
Running this command in a CLI will automatically download all the required packages and set up your project in the following folders:
fuel-station-front/
|-- node_modules/
|-- public/
|-- index.html
|-- favicon.ico
|-- ...
|-- src/
|-- App.js
|-- index.js
|-- ...
|-- package.json
|-- package-lock.json
|-- README.md
This might look complicated, let us go into details file by file:
node_modules/: This folder contains all the packages and dependencies that you will add to your project using npm. In general, you should leave this folder untouched.
public/: This folder contains static files used for your project. It is populated by default with basic files to run the default page that is generated when you initiate a new project.
src/: This folder is the core of your application and contains its source code. This is where we will write the code of our application. It is populated initially with files to run the default page that is generated when you initiate a new project.
package-lock.json: This file is the equivalent of requirement.txt. It contains all the dependencies and exact versions of the packages that are installed using npm.
package.json: This file is the equivalent of setup.py and it specifies project metadata.
Running the project
In this part of the life cycle of the project (basically building and testing locally), we will use a development server which is a built-in feature of the create-react-app tool. This is good for making quick developments but is not robust enough for production purposes, and we will talk about this in another article.
To run your test server, simply go to your project folder and run the following command:
npm start
This will automatically open a new webpage in your default browser with the default page imitated in any new react project.
the "localhost" means we are running the server on our own computer, while ":3000" indicates the port number of the application.
Installing required package
React has a large ecosystem and many packages are open sources and will simplify your life.
In particular, for this project, we are going to use react-Plotly.js, a wrapper of plotly made for react applications.
npm install react-plotly.js plotly.js
Part of very interesting packages, I can also mention React-router-dom, a powerful URL mapper to manage multi-page applications, and uuid, a package designed to generate automatic ids based on the latest standards. I will not use them today, but this is part of the must-known packages.
Improve the coding experience
I will code the project using VSCode. Many extensions are available in order to simplify your life while developing a React web app.
Let's talk briefly about some of them.
ES7 React/Redux/GraphQL/React-Native snippets: This extension provides a lot of shortcuts to quickly set some template code.
Emmet: This plugin allows for fast coding by providing also a lot of shortcuts and abbreviations to automatically generate code.
Prettier: This extension automatically formats your code when you save your files.
Build the layout
Now that our environment is properly set up, we can start to build our application. As mentioned in the previous part, this will be done mainly after modifying the files in the src/ folder.
Let's look a bit at what is inside there:
|-- src/
|-- App.js
|-- index.js
|-- App.test.js
|-- reportWebVitals.js
|-- setupTests.js
|-- index.css
|-- App.css
|-- logo.svg
The only file of interest for us will be App.js and we will go into more detail about it later. Regarding the other files, quickly:
- index.js is responsible for the rendering of the application. We will not touch this file
- reportWebVitals.js is used to report the performance metrics of the web page to an analytic service, and in our level of use of React, will not be used.
- setupTests.js is a file used to set up a testing environment. Also out of the scope of this article and we will leave it untouched
- logo.svg is simply an image used in the default page when you create the project and can be deleted
- App.css and index.css contain some basic .css for your web app, that could eventually be modified to give another style if you need to.
Application Architecture
In react, we build an application by assembling custom components. As a best practice, each component will be made of two files:
- a .js file used to code the logic of the component: what it does, how it manages its internal state, and how it propagates its state to its parents and children components
- a .css file used for the esthetic and formatting of the component
Let's take the example of our application and see how our logic will apply. We want our App to have basically 3 main components:
- A search area form will be used to look for a place and a type of fuel.
- A map that will display the stations, the same way we built it in the previous article
- A table to summarize quickly the main information about the stations around.
This will look something like this:
|-- src/
|-- App.js
|-- App.css
|-- AppComponents/
|-- StationsFilter/
|-- StationsFilter.js
|-- StationsFilter.css
|-- CustomTextBoxes/
|-- ...
|-- StationsMap/
|-- StationsMap.js
|-- StationsMap.css
|-- StationsTable/
|-- ...
|-- index.js
|-- ...
This might look like a lot of files and folders, but it will help keep your project clean and tidy, which will make your life much easier in the future if you need to go back to it to modify or improve your app.
Application basic layout
At this point, you might want to build a rough layout of what your app could look like. If you don't have a web designer to help, the best is to look at examples of modern apps and get inspiration from them. The Internet is full of templates to build nice components, and with basic .css knowledge and the help of those templates, you will already be able to do nice and modern elements.
As we start the project, we want to build a primary basic view just to have a rough idea about how to organize our components. Half of the job will be basically done with such a basic layout as a web application is basically made of boxes in boxes in boxes…
For our web app, let's keep things simple:
Before going further, we also need to talk about design paradigms. In HTML/CSS, you have many ways of organizing your components. My favorite paradigm is the Flexbox paradigm, which provides an easy way to organize boxes within a container box.
Using this paradigm, you set the organization style of your "container box":
- How are aligned the children's boxes, in rows, or in columns?
- Within this structure, how are organized the children's boxes on the main axis?
- Within this structure, how are organized the children's boxes in the orthogonal axis?
The layout used above follows the Flexbox paradigm.
The gray area is the main container. We want it to contain two "div" (which are the box in HTML language) organized in columns: One containing the header (a title with sometimes some menu options) and one containing the main components.
That div will be organized in a row in such a way that it divides the space in two: in the left part, we will have a div (in orange) for the filtering component and the table with the prices, and in the right, the map component which will take the remaining space.
export default function App() {
return (
// the gray box, we want its "organisation" to be in column
//the header, being the first blue box
Gas Station Finder
//the second blue box, containing the main components, which will be organised in row
//Inside that blue box, we have another box "left-section" (the orange box) containing the Filter and the Table
//Finally we also have the other component, our map, inside the second blue box
);
}
, , and will be the custom components that we are going to build in the coming section.
App.css will contain the .css of our container boxes, let's have a look!
.main-components{
height:90vh;
width:100%;
display: flex;
flex-direction: row;
align-items: center;
}
.left-section{
height:100%;
width: 39%;
background-color: #f4f4f4;
display: flex;
flex-direction: column;
align-items: center;
}
display: flex indicates that the containers are "flex-boxes" and that the children's boxes will be organized following this paradigm
flex-direction indicates if the boxes are organized in a row or in a column
align-items organize the alignment in the secondary direction
justify-content (not used here) organize the alignment in the main direction
Initiate the components
Now that all of the above is set up, let's initialize our components with a basic version for all of them, which we will improve over time.
In StationsFilter.js, we are going to use the shortcut "rfc" (as shown in the .gif in the previous section) that will directly generate the template for our component, and also add an import statement for the corresponding .css file.
import "./StationsFilter.css";
export default function StationsFilter() {
return (
StationsFilter
);
}
This little piece of code indicates with "export default" that the function StationsFilter can be later directly imported in another file like:
import StationsFilter from 'pathtofile/StationsFilter';
and used in its own HTML tag:
Let's make the same template for StationsMap.js and StationsTable.js, I will pass on the code snapshot here, which is exactly identical to what we did before.
Working on the Filter Layout
We want the filter to be made of two components:
- One to look for a place by postal code
- One to select the fuel type
import "./StationsFilter.css";
export default function StationsFilter() {
const fuelTypes = ["E10","E85","Gazole","GPLc","SP95","SP98"]
return (
);
}
Let's have a look in detail:
- We first define a box of type "form" in which we will put our form fields and the button
- We create an input of type "text" used to receive text. We place a placeholder name "Postal Code" which will appear to give an indication to the customers and a className for the .css
- We create a Dropdown with the tag .
- Inside this Dropdown component, we need to add the options as "children" tags. This is done by iterating via an array (the equivalent of a python list) to automatically generate all the possibilities.
const fuelTypes = ["E10","E85","Gazole","GPLc","SP95","SP98"]
is equivalent to:
const fuelTypes = ["E10","E85","Gazole","GPLc","SP95","SP98"]
We use the "map" version because it is more compact, and potentially easier to maintain: if a new fuel type appears, we can simply modify our fuelTypes array and it will generate automatically the corresponding options.
- Finally, we create a button that will be later the entry point for sending a request to the server and retrieve some data to display
Note: currently nothing is interactive, this will be detailed in the next article.
Note 2: I will not detail the .css here, for most of it I took it by looking for inspiration on the internet and it is not necessarily very interesting to detail here.
This is how looks the component on its own:
Building the Map
In order to build the map, we are going to use the react-plotly plugin.
In react, plotly figures are basically made the same way as in python, with traces and a layout. The main difference is that instead of using a wrapper to build our charts (like go.Scatter) we need to provide the data in the JSON format (a dictionary with keys/values).
If you are used to building your charts in python, don't panic, it is actually very simple to convert your python figure to a react figure.
As a starting point for now and for the illustration, I am going to show you how to copy our python figure to a react one. Later, when we will add interactions, we will rebuild this map adding good data feeds. This is the map we want to copy from our python code:
First step: let's create a .json copy of our chart:
fig_json_path = "fig.json"
#Convert the file to a json string, and saved in in the computer
fig_as_json = fig.to_json()
with open(fig_json_path, 'w') as f:
f.write(fig_as_json)
If you want to have a look at how looks your figure as a python dictionary, you can also do fig.to_dict(). In the case of the figure above, this will give something looking like this:
{
"data":[trace1, trace2 ...],
"layout":figure_layout
}
with, for example, taking the layout and one of the traces of our figure:
trace_stations = {
'lat': stations_lat,
'lon': stations_lon,
'marker': {'color': stations_price,
'colorscale': colorscale
'size': 40},
'mode': 'markers',
'opacity': 0.7,
'showlegend': False,
'text': stations_price,
'type': 'scattermapbox',
'uid': '911aadec-c42e-4eb7-968a-6e489606224e'
}
figure_layout = {
'height': 600,
'width': 600
'mapbox': {'accesstoken': token,
'center': {'lon': center_lon, 'lat': center_lat},
'style': 'streets',
'zoom': zoom,
'bearing': 0,
'pitch': 0},
}
Most of the fields are generally familiar if you are used to plotly. Note the field "type" which specifies the type of trace you are building. In our case "type":"scattermapbox", equivalent to what we did in python with (go.Scattermapbox() )
This can be used directly in react and it is reproducible for any type of figure making it very easy to go from a proof of concept in python to its counterpart in react.
Let's copy the full JSON we generated before in our project and include it in the folder of the component StationsMap.
|-- src/
|-- App.js
|-- App.css
|-- AppComponents/
|-- StationsHeader/
|-- StationsMap/
|-- StationsMap.js
|-- StationsMap.css
|-- fig.json
We can now simply import the file in our StationsMap.js script and use it in a react-plotly Figure.
import "./StationsMap.css"
import Plot from "react-plotly.js";
import figJson from "./fig.json"
export default function StationsMap() {
const data = figJson["data"]
const layout = figJson["layout"]
layout["autosize"]=true
layout["margin"]={"b":0,"t":0,"l":0,"r":0}
delete layout.template
delete layout.width
delete layout.height
return (
)
}
Let's have a look in detail:
First, we import simply our JSON object and store it in the variable figJson. Done this way, the figJson is a javascript Object, which is the equivalent of Python dictionaries which will make the job for our current static view.
import figJson from "./fig.json"
Then, as we could do in python, we update our layout Object with extra information and remove some keys/values. In my case, I set the plotly layout to "autosize" which means it will take all the space available, remove all the margin around the figure and delete the key/values for "template", "width", and "height " that where generated by default from the python figure.
layout["autosize"]=true
layout["margin"]={"b":0,"t":0,"l":0,"r":0}
delete layout.template
delete layout.width
delete layout.height
Finally, I declare that the component returned by my StationsMap() function is a plotly component including all the traces from my son and the modified layout.
return (
)
Note that you can pass parameters to a component directly inside the tag using the format:
In the case of a plotly component, we pass a parameter data which is a list of objects representing the different traces of the chart, a parameter layout which is an object containing the layout information of the chart, and extra parameters like "style" or "useResizeHandler" which are parameters to indicate that the div generated by plotly should take all the remaining space.
Building the Table
We are missing one last component to complete our App Layout: a table summarizing the information of the surrounding stations.
Tables are components that can be difficult to design because we need to find the right level of information to show. They are usually more difficult to interpret than charts, but they are still very useful when you want an overall view of the details of your data.
In the case of our component, we want to provide some of the raw data available such as:
- the address of the station
- the price of the fuel of interest which is expressed in €/L.
On top of that, we can as well transform the data to be more concrete for a user, for example:
- the total price of an average full tank (say 60L)
- he potential saving or loss in comparison to the average places of the surrounding
- and why not add a Google Map link so that they can be redirected to Google services to get more information about the provided address?
This looks like nothing, but putting yourself in the shoes of your customers is very important. The more clarity you bring to them in one look at the data, the more they will like your application and come back.
Given that we are still in a "static view mode", I will generate a sample of data in python based on the default map and use that at first for my layout. There are multiple ways of representing a table in a json, I chose to use a list of Objects that I find personally more convenient:
dataTable = [
{
"address":address1, "price_per_L":price_per_L1, "price_tank":price_tank1,
"delta_average":delta_average1,"google_map_link":google_map_link
},
{
"address":address2, "price_per_L":price_per_L2, "price_tank":price_tank2,
"delta_average":delta_average2, "google_map_link":google_map_link
},
etc...
]
I'll add some tips:
- If you are more comfortable in Python than in Javascript, it is fine. In that case, make all your data transformations in Python. We will see in one of the coming articles how to connect our React application to a Python API where most of the data transformation logic will be handled.
- Using pandas, you can very easily convert a DataFrame to a list of objects and dump it as a json using:
with open("sample_table.json", "w") as f:
json.dump(df.to_dict("records"), f)
As with the plotly figure, let's copy the file to the relevant folder
|-- src/
|-- App.js
|-- App.css
|-- AppComponents/
|-- StationsHeader/
|-- StationsMap/
|-- StationsTable/
|-- StationsTable.js
|-- StationsTable.css
|-- sample_table.json
Now that we have a base of work, let's code the table. This is the general layout in HTML:
Address
Price
Full Tank
Saved
Some values
Some values
Some values
Some values
...
...
The
a cell in that line. This means that if you have 4 cells in your header, you should define your cells in your rows with also 4 cells.
Now let's use our static data. As in the last part of the graph, we start by importing the pre-generated .json with the data:
Remember that this tableJson is an Array of Objects. We can use again the Array.map() method to iterate through the Objects of the array and generate dynamically our table.
A few comments here:
We have now a very simple version of the table which looks like this, after a bit of formatting with some .css: This version is not a bad start, but we can certainly improve it. First, we can add the links to Google Maps as mentioned above. You can pre-compute them from the address of the station using the below formula:
Then we can generate a hyperlink from the cell of the address in our table passing that google link in a tag:
We want to make a last improvement to this table. You might have noticed I added in my python table a column named "better than average"? This is a value that will help us create a color mapping to quickly see the cheap stations to the expensive ones. Let's create a variable color in our mapping function that is by default black, but that can become red or green depending on the "better than average" field, and let's pass that color to the style of the last cell of each row:
And let's have a look at our final result: Our table is ready, we can now see very easily which stations are the more competitive in terms of price, and by a simple click, check their location in Google Maps! Final App LayoutAt that point, our layout is finalized. We already implemented the main App layout in the first chapter:
So we can simply look at http://localhost:3000/ and check what it looks like: Note that if you are not satisfied with this layout, given the modularity of our code, it is very simple to change it to a different configuration. (In our next article, we will also talk about application responsiveness) ConclusionIn this article, we continued the work initiated last time about our Station Finder Application. In particular, we started to look in detail at the framework React to build robust and highly customizable Web Applications. We reach a point where we can run a static version of the application on a test server locally, but we are still far from having an application fully operational. Our React prototype is not done yet, and in the next article, we will continue to explore React, focusing this time on interactive features. CommentRecommend |
---|