How to create a Contact App in ReactPy

reactpy contact

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.

contact design

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
    }
),
  1. id has a value of "name", the value of "html_for" should be the same to enforce binding.
  2. 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

name. The code below shows how to manage variables in a component.
...

@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"])
    
...

email
...
def email_event_handler(event):
    """Extract the email input."""
    set_email(event["target"]["value"])

...

submit
...
@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.
contact image body
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

E. Summary

The contact application is built with the use of form, label, input and button components. The use_state hook manages the variable name, phone number, email and records. Saved records can be exported to csv file.

F. References