A. Introduction
A contact application manages information such as names, phone numbers, emails, birth date and others. It can store and display the information effectively better than using a physical contact book.
B. User interface building process
Let us create a user interface that allows users to enter their names, phone numbers, and email addresses. We will include a submit button to save the information and another button to save the records to a CSV file. Moreover, this application will use use_state() hook to handle variables for name, phone numbers, email and records.
It would look like this.
You need to install ReactPy to run this application. You may look at this guide on how to install it.
C. Code Snippet
Save the code in contact.py file.
1. doctring
Add a docstring
at the top. The docstring allows the user to know what it is without examining the whole code.
contact.py
"""Contact Application It stores and display names, phone numbers and email addresses. """ ...
2. imports
We need to import modules that we will be using. We will also use the fastapi backend.
"""Contact Application It stores and display names, phone numbers and email addresses. """ from reactpy import component, html, hooks, event from reactpy.backend.fastapi import configure from fastapi import FastAPI ...
3. Contact component
Build a Contact component so we can use it in other applications.
... from fastapi import FastAPI @component def Contact(): ... app = FastAPI() configure(app, Contact)
4. Form
To organize a bit we will use the form element along with the labels and inputs elements. We wrap the form in a div in case we apply some styling.
... @component def Contact(): return html.div( # Declare variables ... # Declare event handlers ... html.form( {'on_submit': submit}, html.label(), html.input(), ), # Print the saved contact info. ) app = FastAPI() configure(app, Contact)
Breakdown this form
into labels and input elements.
a. Label
html.label( {"html_for": "name"}, "Name" )
The html_for is equivalent to for in HTML. We use this naming as for is a keyword in Python. We will bind a pair of label/input through the value of html_for.
Here is a typical example of binding a label and input elements through html_for from label and id from input.
html.label( {"html_for": "name"}, "Name:" ), html.input( { "type": "text", "id": "name", "placeholder": "enter your name", } )
Notice the values of html_for and id are the same. Bind means whenever you press the label, the focus will move to the paired input.
b. Input
html.input( { "type": "text", "id": "name", "placeholder": "enter your name", "on_change": name_event_handler } ),
- id has a value of "name", the value of "html_for" should be the same to enforce binding.
- on_change the value of this attribute should point to a function that is executed whenever there is a change in the input. We need to define this name_event_handler above the form.
c. Event handler
def name_event_handler(event): """Extract the name input.""" name_value = event["target"]["value"] set_name(name_value)
The set_name is a function that assigns the value to a variable
... @component def Contact(): # Define variables. name, set_name = hooks.use_state('') phone_number, set_phone_number = hooks.use_state('') email, set_email = hooks.use_state('') records, set_records = hooks.use_state([]) def name_event_handler(event): """Extract the name input.""" set_name(event["target"]["value"]) ...
The hook function use_state() will return a tuple of (variable, variable_updater_function)
. It can store a string, number, list and other data types. The records variable stores a list in this case.
We need 3 more event handlers, for phone, email and saving the records. Except for the submit event handler, they are similar to name_event_handler.
phone number... def phone_event_handler(event): """Extract the phone number input.""" set_phone_number(event["target"]["value"]) ...
... def email_event_handler(event): """Extract the email input.""" set_email(event["target"]["value"]) ...
... @event(prevent_default=True) def submit(event): set_records(records + [[name, phone_number, email]]) ...
If we run the app now with:
uvicorn contact:app
We will get this:
That is ugly, but it worked. We need to apply some styling and place them vertically. For example we can use break between label and input.
... # 1. Name html.label( { 'html_for': 'name' }, 'Name' ), html.br(), # this html.input( { "type": "text", "id": "name", "placeholder": "enter your name", "name": "name", "on_change": name_event_handler } ), ...
We also need to apply a paragraph element to separate the label/input pair and submit.
This looks much better. We can style this to improve the visual, either by using raw css or adapt some framework such as bootstrap and others. I will probably cover this in another post. And yes bootstrap styling is now in tictactoe, diary, income and expenses and navigation bar apps.5. Saving to CSV
CSV stands for comma-separated values. Our records are in a list of list. We will utilize the pandas library to convert this list to a csv file.
Install pandas with:
pip install pandas
Then import it.
... import pandas as pd ...
We save the records whenever users press the "Export To CSV" button.
... html.button({"on_click": export_to_csv}, "Export To CSV"), html.p(f'records: {records}'), ...
And this is our event handler to export records to csv file.
... def export_to_csv(event): """Saves records to csv file. Items from records are saved in overwrite mode. Previous content will be overwritten. """ df = pd.DataFrame(records, columns=['Name', 'Phone', 'Email']) df.to_csv('records.csv', index=False) ...
Data records that are not submitted yet will not be exported to csv. See the full code in the next section.
D. Full Code
contact.py"""Contact Application It stores names, phone numbers and email addresses. """ from reactpy import component, html, hooks, event from reactpy.backend.fastapi import configure from fastapi import FastAPI import pandas as pd @component def Contact(): # Define variables. name, set_name = hooks.use_state('') phone_number, set_phone_number = hooks.use_state('') email, set_email = hooks.use_state('') records, set_records = hooks.use_state([]) # Define event handlers. def name_event_handler(event): """Extract the name input.""" set_name(event["target"]["value"]) def phone_event_handler(event): """Extract the phone number input.""" set_phone_number(event["target"]["value"]) def email_event_handler(event): """Extract the email input.""" set_email(event["target"]["value"]) @event(prevent_default=True) def submit(event): """Stores data to a record variable in memory""" set_records(records + [[name, phone_number, email]]) def export_to_csv(event): """Saves records to csv file. Items from records are saved in overwrite mode. Previous content will be overwritten. """ df = pd.DataFrame(records, columns=['Name', 'Phone', 'Email']) df.to_csv('records.csv', index=False) # Return the form. return html.div( html.form( {'on_submit': submit}, # 1. Name html.label( { 'html_for': 'name' }, 'Name' ), html.br(), html.input( { "type": "text", "id": "name", "placeholder": "enter your name", "name": "name", "on_change": name_event_handler } ), html.p(), # 2. Phone number html.label( { 'html_for': 'phone_number' }, 'Phone Number' ), html.br(), html.input( { "type": "text", "id": "phone_number", "placeholder": "enter your phone number", "on_change": phone_event_handler } ), html.p(), # 3. Email html.label( { 'html_for': 'email' }, 'Email' ), html.br(), html.input( { "type": "email", "id": "email", "placeholder": "enter your email", "on_change": email_event_handler } ), html.p(), # 4. Submit html.input({'type': 'submit'}), html.p(), ), html.button({"on_click": export_to_csv}, "Export To CSV"), html.p(f'records: {records}'), ) app = FastAPI() configure(app, Contact)
Run the app with:
uvicorn contact:app