Article Streamlit Part 4: Form Validation Part 2

Article Streamlit Part 4: Form Validation Part 2

Hiking Through Real-Time Validation with Streamlit

Under the clear blue skies of Austin, Texas, Rick and Chris embarked on a challenging hike through the springs and canyons, each carrying a 45-pound rucksack. Their loyal dogs trotted alongside them, tails wagging with excitement. As they navigated the winding trails, the conversation naturally shifted to their latest coding endeavors.

Rick: Adjusting his rucksack "You know, Chris, just like these trails keep us on our toes, I want my Streamlit app to keep users engaged with real-time form validation."

Chris: "Ah, stepping up your game, I see. Real-time validation can definitely enhance user experience. What's on your mind?"

Rick: "Well, in my previous app, users only found out about validation errors after submitting the form. It's like hiking all this way only to realize we forgot our snacks!"

Chris: Laughs "Can't have that! Implementing real-time validation in Streamlit is like having a trail map that updates as we walk—users get immediate feedback."

Rick: "Exactly! But I'm not entirely sure how to manage the validation state and provide that immediate feedback. Any pointers?"

Chris: "Sure thing. It's all about leveraging Streamlit's session state and reactive programming model. Think of session state as your trusty backpack—keeps everything you need within reach."

Rick: "Makes sense. And I guess real-time feedback is like our dogs alerting us to any obstacles ahead?"

Chris: "You got it! Let's dive into it when we take a break. For now, let's enjoy this hike and let the ideas flow as freely as the springs around us."

They continued their journey, the synergy between physical exertion and mental stimulation fueling their progress.


Real-Time Form Validation in Streamlit: Advanced Techniques

In this tutorial, we'll explore methods to enhance user experience by providing immediate feedback during data entry using Streamlit. By implementing real-time form validation, we can improve data quality and user satisfaction, ensuring a seamless and efficient interaction process.

Why Real-Time Validation?

  • Immediate Feedback: Users receive instant validation messages, reducing confusion and frustration.
  • Improved Data Quality: Errors are caught and corrected on the spot.
  • Enhanced User Experience: A responsive form feels more interactive and engaging.

Basically, as soon as you tab out of a form field, you'll get validation feedback for that field instead of waiting until you submit the entire form.


To see this article with syntax highlighting for code listings, diagrams, and images go to my website and see Article Streamlit Part 4: Form Validation Part 2.





Setting Up the Project

First, please make sure you have Streamlit installed and set up your project environment.

Create and activate a virtual environment:

# Create and activate virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\\\\Scripts\\\\activate

        

You can also use conda or other virtual environments. Python has many choices. I prefer using a virtual environment and usually use conda. You should use one so you don’t have package conflicts between projects.

Installing Conda:

Before creating a virtual environment with Conda, you need to install it first. Here are the commands for different operating systems:

For Ubuntu/Debian (using apt-get):

sudo apt-get update
sudo apt-get install conda
        

For Windows (using Chocolatey):

choco install miniconda3
        

For macOS (using Homebrew):

brew install --cask miniconda
        

Creating a Conda Virtual Environment:

Once Conda is installed, you can create and activate a virtual environment:

# Create a new environment named 'streamlit_env'
conda create --name streamlit_env python=3.12

# Activate the environment
conda activate streamlit_env

# Install Streamlit in the activated environment
pip install streamlit
        

After creating and activating the Conda environment, you can install Streamlit and create your application file as shown in the next steps.

Install Streamlit:

pip install streamlit

        

Create the application file:

touch form_validation2.py

        

Step-by-Step Guide to Real-Time Form Validation

1. Import Required Libraries

import streamlit as st
from datetime import datetime, date, timedelta
import re

        

  • streamlit: For building the app.
  • datetime: For handling date inputs.
  • re: For regular expression matching.

2. Define Validation Functions

These functions validate user input and provide error messages.

2.1 Name Validation

def validate_name(name, field_name):
    if not name:
        return False, f"{field_name} is required"
    if not re.match(r"^[a-zA-Z'\\\\s\\\\-]{2,50}$", name):
        return False, f"{field_name} must contain only letters, spaces, hyphens, or apostrophes (2-50 characters)"
    return True, ""

        

  • Checks: Input is not empty. Contains only allowed characters. Length between 2 and 50 characters.

The name validation function performs the following checks:

  • Ensures the input is not empty by checking if the name string has a value.
  • Uses a regular expression to verify that the name contains only letters, spaces, hyphens, or apostrophes.
  • Confirms the length of the name is between 2 and 50 characters.

If any of these checks fail, the function returns False along with an appropriate error message. If all checks pass, it returns True with an empty string, indicating a valid name input.

2.2 Email Validation

def validate_email(email):
    if not email:
        return False, "Email is required"
    if not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$", email):
        return False, "Invalid email format"
    return True, ""

        

  • Checks: Input is not empty. Matches standard email format.

The email validation function performs the following checks:

  • Ensures the email input is not empty by checking if the email string has a value.
  • Uses a regular expression to verify that the email follows a standard format:
  • Allows alphanumeric characters, periods, underscores, percent signs, plus signs, and hyphens before the @ symbol.
  • Requires an @ symbol followed by a domain name.
  • The domain must contain alphanumeric characters, periods, and hyphens.
  • Ends with a period followed by at least two letters (e.g., .com, .org, .co.uk).

If any of these checks fail, the function returns False along with an appropriate error message. If all checks pass, it returns True with an empty string, indicating a valid email input.

Here's a flow diagram describing the validation process:


graph TD
    A["Start"] --> B{"Input provided?"}
    B -- Yes --> C["Clean input"]
    B -- No --> D["Return False, 'Input required'"]
    C --> E{"Matches pattern?"}
    E -- Yes --> F["Return True, ''"]
    E -- No --> G["Return False, 'Invalid format'"]
    D --> H["End"]
    F --> H
    G --> H
        

This diagram illustrates the general flow of the validation functions, including checking for input presence, cleaning the input (for phone numbers), pattern matching, and returning appropriate validation results.

To see this article with syntax highlighting for code listings, diagrams, and images go to my website and see Article Streamlit Part 4: Form Validation Part 2.

2.3 Phone Validation

def validate_phone(phone):
    if not phone:
        return False, "Phone number is required"
    clean_phone = re.sub(r'[\\\\s\\\\-\\\\(\\\\)]', '', phone)
    if not re.match(r'^[0-9]{10}$', clean_phone):
        return False, "Phone must be 10 digits"
    return True, ""

        

  • Checks: Input is not empty. Contains exactly 10 digits after removing common formatting characters.

Similar to the email and phone validation but the regex matches a standard phone number.

2.4 Age Validation

def validate_age(age):
    if age <= 0:
        return False, "Age must be greater than 0"
    if age > 120:
        return False, "Age must be less than 120"
    return True, ""

        

  • Checks: Age is a positive number and less than 120.

This validation is simpler, only checking a range. Note that there are no built-in validators per se. It's just plain Python code, which is a good thing if you're a minimalist who wants to get stuff done efficiently.

2.5 Date Validation

def validate_date(check_date):
    if not check_date:
        return False, "Date is required"
    if check_date > date.today():
        return False, "Date cannot be in the future"
    return True, ""

        

  • Checks: Input is not empty. Date is not in the future.

Once again, this is just simple, easy-to-understand Python code. No special frameworks needed.


3. Initialize Session State

if 'form_submitted' not in st.session_state:
    st.session_state.form_submitted = False

if 'validation_messages' not in st.session_state:
    st.session_state.validation_messages = []

        

  • Purpose: To persist validation status and form submission state across app interactions.

The above code initializes the session state. Streamlit apps rely heavily on session state management to control app logic. This pattern of checking if a value is already in the state and initializing it if not is fairly standard in a Streamlit app. Once again, it's pretty easy to understand and follow Python code. Nothing fancy here. If you've worked with other UI frameworks, where fanciness is the rule with all sorts of callbacks and such, you'll find this refreshing. This is just normal-looking Python code.


4. Create the Form Layout

form_col, validation_col = st.columns([2, 1])

with form_col:
    st.header("?? Personal Information")
    # ... (form fields go here)

        

  • Layout: Uses columns to separate the form from the validation summary.

The st.columns() function in Streamlit is used to create a horizontal layout with multiple columns. Here's a breakdown of how it works:

  • Purpose: It allows you to organize your Streamlit app's content into side-by-side columns, creating a more structured and visually appealing layout.
  • Usage: You pass a list of numbers to st.columns(), where each number represents the relative width of each column.
  • Example: col1, col2 = st.columns([2, 1]) creates two columns, where the first column is twice as wide as the second.
  • Content Placement: You can add elements to specific columns using a with statement or by calling methods directly on the column objects.
  • Flexibility: This function is particularly useful for creating responsive layouts that adapt well to different screen sizes.

In the context of our form validation app, using st.columns() allows us to separate the form inputs from the validation summary, creating a more organized and user-friendly interface.

This code is creating a layout for the form section of the Streamlit app. Here's what it's doing:

  • Creating a form column: The with form_col: statement indicates that all the following Streamlit commands will be placed inside the previously defined form_col column.
  • Adding a header: st.header("?? Personal Information") creates a header in the form column with the text "Personal Information" and a person emoji (??).

This structure helps organize the app's layout, putting the form fields under a clear heading within a specific column. It improves readability and user experience by clearly separating the form section from other parts of the app.


5. Implement Real-Time Validation

For each input field, we perform validation as the user types.

5.1 First Name Field

first_name = st.text_input("First Name", key="first_name")
if first_name:
    is_valid, message = validate_name(first_name, "First name")
    st.session_state.first_name_valid = is_valid
    if not is_valid:
        st.error(message)
    else:
        st.success("Valid first name!")
else:
    st.session_state.first_name_valid = False

        

  • Process: Validates the input. Updates the session state with validation status. Provides immediate feedback.

Here we use st.text_input to create an input field for First Name. The key parameter is how we refer to it later. We extract the first_name from the text input field. If it's missing, we mark the first_name field as invalid by storing first_name_valid in the session as false: st.session_state.first_name_valid = False. If the first name is present, we use validate_name(...) to validate it. We get the returned message and update the session state first_name_valid. If it's not valid, we take the message we got from the validate_name function and display it with st.error. Once again, this is straightforward Python code that's easy to understand and modify without having to learn a complex framework.

Here's a Mermaid flow diagram describing the process of real-time validation for form fields:

graph TD
    A[User enters data] --> B{Is field empty?}
    B -- Yes --> C[Mark field as invalid]
    B -- No --> D[Validate input]
    D --> E{Is input valid?}
    E -- Yes --> F[Mark field as valid]
    E -- No --> G[Display error message]
    F --> H[Display success message]
    G --> I[Update session state]
    H --> I
    I --> J[Update validation summary]
    J --> K[User continues to next field]
    K --> A
        

To see this article with syntax highlighting for code listings, diagrams, and images go to my website and see Article Streamlit Part 4: Form Validation Part 2.

This diagram illustrates the flow of real-time validation for each form field:

  1. User enters data into a field
  2. The system checks if the field is empty
  3. If not empty, it validates the input
  4. Based on validation, it marks the field as valid or invalid
  5. It displays appropriate messages (success or error)
  6. The session state is updated
  7. The validation summary is updated
  8. The process repeats for each field as the user continues filling the form

5.2 Repeat for Other Fields

The other field logic looks a lot like the above. They just call the right corresponding validate method.

  • Last Name
  • Email
  • Phone
  • Age
  • Birth Date
  • Start Date and End Date

The code for Last Name, Phone, Age, Birth Date all pretty much follow the same pattern as First Name.


6. Check Overall Validation Status

def get_validation_status():
    validation_messages = []

    # Example for first name
    if not st.session_state.get('first_name_valid', False):
        if st.session_state.get('first_name', ''):
            is_valid, message = validate_name(st.session_state.first_name, "First name")
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("First name is required")

    # Repeat for other fields...

    return validation_messages

        

  • Purpose: Collects all validation messages to display in the summary.

The get_validation_status method collects all of the validation messages so we can display them all at once. We display validation message by the field and also all at once.

Here's a Mermaid flow diagram describing the process of form submission and validation:

graph TD
    A[User clicks Submit button] --> B{Get validation status}
    B --> C{Any validation messages?}
    C -- No --> D[Display success message]
    D --> E[Set form_submitted to True]
    C -- Yes --> F[Display error message]
    F --> G[List validation errors]
    G --> H[User corrects errors]
    H --> A
        

To see this article with syntax highlighting for code listings, diagrams, and images go to my website and see Article Streamlit Part 4: Form Validation Part 2.

This diagram illustrates the flow of the form submission process:

  1. User clicks the Submit button
  2. The system retrieves the validation status
  3. It checks if there are any validation messages
  4. If there are no messages, it displays a success message and marks the form as submitted
  5. If there are messages, it displays an error and lists the validation errors
  6. The user then corrects the errors and can submit again

This process ensures that the form is only successfully submitted when all fields pass validation, providing clear feedback to the user about any issues that need to be addressed.

Thus, get_validation_status method gets called when the form is submitted.

        if st.button("Submit"):
            # Get validation messages
            validation_messages = get_validation_status() ## <---

            if not validation_messages:
                st.success("Form submitted successfully!")
                st.session_state.form_submitted = True
            else:
                st.error("Please fix the following validation errors:")
                for message in validation_messages:
                    st.error(f"? {message}")
        

This code snippet handles form submission and validation. Here's a breakdown of its functionality:

  1. It creates a "Submit" button using st.button("Submit").
  2. When the button is clicked, it triggers these actions: Calls the get_validation_status() function to check for any form validation errors. If there are no validation messages (all fields are valid): Displays a success message using st.success(). Sets the session state variable form_submitted to True, indicating successful submission.

  • If there are validation messages this means that some fields are invalid: Displays an error message using st.error(). Iterates through each validation message, displaying them as separate errors using st.error().

This code ensures form submission only happens when all fields are valid, providing clear feedback to users about any errors that need correction before submission.


7. Display Validation Summary

with validation_col:
    st.header("Validation Summary")
    all_valid = not get_validation_status()
    if all_valid:
        st.success("All fields are valid! ?")
    else:
        messages = get_validation_status()
        st.error("Fields needing attention:")
        for message in messages:
            st.warning(f"? {message}")

        

  • Feedback: Provides users with an overview of which fields need correction.

I added some helpful sidebars just for the tutorial so it aids in the display, and getting a feel for the error messages.

Here's a Mermaid flow diagram describing the process of updating the sidebar with form data:

graph TD
    A[Start update_sidebar_table function] --> B[Set sidebar title to 'Form Data']
    B --> C[Create dictionary of current form data]
    C --> D[Initialize table header]
    D --> E[Initialize empty table rows]
    E --> F[Loop through each field in current_data]
    F --> G[Get field value from session state]
    G --> H[Determine field validation status]
    H --> I[Format table row with field, value, and status]
    I --> J{More fields?}
    J -- Yes --> F
    J -- No --> K[Combine header and rows into complete table]
    K --> L[Display table in sidebar using markdown]
    L --> M[Create 'Clear Form' button in sidebar]
    M --> N{Clear Form clicked?}
    N -- Yes --> O[Clear all session state keys]
    O --> P[Rerun the app]
    N -- No --> Q[End update_sidebar_table function]
        

This diagram illustrates the step-by-step process of updating the sidebar with the current form data and validation status:

  1. The function starts by setting the sidebar title.
  2. It creates a dictionary of the current form data from the session state.
  3. It initializes the table header and rows.
  4. For each field in the form data, it:

  • Retrieves the value from the session state
  • Determines the validation status
  • Formats a table row with this information

  1. After processing all fields, it combines the header and rows into a complete table.
  2. The table is displayed in the sidebar using markdown.
  3. A 'Clear Form' button is added to the sidebar.
  4. If the button is clicked, all session state keys are cleared and the app is rerun.

This flow demonstrates how the sidebar is dynamically updated to reflect the current state of the form, providing real-time feedback to the user.


8. Update Sidebar with Form Data

def update_sidebar_table():
    st.sidebar.title("?? Form Data")

    current_data = {
        'First Name': st.session_state.get('first_name', ''),
        'Last Name': st.session_state.get('last_name', ''),
        'Email': st.session_state.get('email', ''),
        # ... other fields ...
    }

    table_header = "| Field | Value | Status |\\\\n|--------|--------|--------|\\\\n"
    table_rows = ""

    for field, value in current_data.items():
        # Format value and determine status
        field_key = field.lower().replace(' ', '_')
        is_valid = st.session_state.get(f'{field_key}_valid', False)
        status = "?" if is_valid and value else "?" if value else "?"
        table_rows += f"| {field} | {value} | {status} |\\\\n"

    st.sidebar.markdown(table_header + table_rows)

    if st.sidebar.button("Clear Form"):
        for key in st.session_state.keys():
            del st.session_state[key]
        st.rerun()

        

  • Purpose: Shows current form data and validation status in the sidebar.

I created this for debugging and to show for form status for debugging and understanding the code.


9. Handle Form Submission

if st.button("Submit"):
    validation_messages = get_validation_status()

    if not validation_messages:
        st.success("Form submitted successfully!")
        st.session_state.form_submitted = True
    else:
        st.error("Please fix the following validation errors:")
        for message in validation_messages:
            st.error(f"? {message}")

        

  • Process: On click, checks if all validations pass. If valid, confirms submission. If not, displays errors.

We described this before, but here it is again for completeness.

Writing complex inter dependent form validation

The tricky part sometimes is when the validation depends on how some form fields relate to others. Well, this is no real problem for Streamlit. As always, it's just plain, easy-to-read Python.

def get_validation_status():
    """Get detailed validation status for all fields"""
    validation_messages = []

    # Check each field and collect validation messages
    if not st.session_state.get('first_name_valid', False):
        if st.session_state.get('first_name', ''):
            is_valid, message = validate_name(st.session_state.first_name, "First name")
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("First name is required")

    .... # other fields 
    if not st.session_state.get('birth_date_valid', False):
        if st.session_state.get('birth_date'):
            is_valid, message = validate_date(st.session_state.birth_date)
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Birth date is required")

    # Check date range
    start_date = st.session_state.get('start_date')
    end_date = st.session_state.get('end_date')
    
    if start_date and end_date and start_date >= end_date:
        validation_messages.append("Start date must be before end date")

    return validation_messages
    
        

Here's a Mermaid flow diagram describing the date range validation process:

graph TD
    A[Start get_validation_status] --> B{Check start_date and end_date}
    B --> |Both dates present| C{Is start_date >= end_date?}
    C --> |Yes| D[Add error message to validation_messages]
    C --> |No| E[No action needed]
    B --> |One or both dates missing| E
    D --> F[Return validation_messages]
    E --> F
        

This diagram illustrates the flow of the date range validation:

  1. The process starts with the get_validation_status function.
  2. It checks if both start_date and end_date are present.
  3. If both dates are present, it compares them to see if the start date is greater than or equal to the end date.
  4. If the start date is indeed greater than or equal to the end date, an error message is added to the validation_messages list.
  5. If the dates are in the correct order, or if one or both dates are missing, no action is taken for this specific validation.
  6. Finally, the function returns the validation_messages list, which may or may not include the date range error message.

The validation for the start_date and end_date fields demonstrates how Streamlit allows for straightforward implementation of complex, interdependent form validations using plain Python code. Let's break down how this works:

# Check date range
start_date = st.session_state.get('start_date')
end_date = st.session_state.get('end_date')

if start_date and end_date and start_date >= end_date:
    validation_messages.append("Start date must be before end date")
        

This code snippet shows several key aspects of Streamlit's form validation capabilities:

  • Accessing form data: The values for start_date and end_date are retrieved from Streamlit's session state using st.session_state.get(). This allows easy access to the current values of these fields.
  • Simple conditional logic: The validation uses a straightforward if statement to check if both dates are present and if the start date is greater than or equal to the end date.
  • Interdependent validation: The validation rule compares two separate form fields (start_date and end_date), demonstrating how easy it is to implement complex, relational validations in Streamlit.
  • Clear error messaging: If the condition is met (i.e., the dates are in the wrong order), a descriptive error message is added to the validation_messages list.

This approach showcases how Streamlit leverages Python's simplicity and readability to handle complex form validations. Developers can easily understand and modify this code without needing to learn a complex framework or special syntax. The validation logic is expressed in plain Python, making it accessible and maintainable.

Now, let's take a closer look at how we implement the date range validation in our Streamlit form. This code snippet demonstrates the real-time validation of the start and end dates:

        with col3:
            start_date = st.date_input("Start Date", key="start_date")
        with col4:
            end_date = st.date_input("End Date", key="end_date")

        if start_date and end_date:
            if start_date >= end_date:
                st.error("Start date must be before end date")
                st.session_state.start_date_valid = False
                st.session_state.end_date_valid = False
            else:
                st.success("Valid date range!")
                st.session_state.start_date_valid = True
                st.session_state.end_date_valid = True
        

The above demonstrates how to implement real-time validation the date range inputs in a and show the error messages next to the date form fields. Here's a breakdown of what it does:

  • It creates two date input fields using Streamlit's st.date_input() function: one for the start date and one for the end date. The same ones we showed validating earlier from the session.
  • The inputs are placed in separate columns (col3 and col4) for better layout.
  • After the user selects both dates, the code checks if both dates are present (not None).
  • If both dates are selected, it compares them like before:
  • If the start date is greater than or equal to the end date, it displays an error message using st.error() and sets the validation state for both date fields to False.
  • This real-time validation provides immediate feedback to the user that is UI context-appropriate—right next to the field. It ensures that the selected date range is valid before form submission, enhancing user experience by catching errors early in the input process.


Full Code Listing

Here's the complete code for the real-time form validation application.

import streamlit as st
from datetime import datetime, date, timedelta
import re

def validate_name(name, field_name):
    if not name:
        return False, f"{field_name} is required"
    if not re.match(r"^[a-zA-Z'\\s\\-]{2,50}$", name):
        return False, f"{field_name} must contain only letters, spaces, hyphens, or apostrophes (2-50 characters)"
    return True, ""

def validate_email(email):
    if not email:
        return False, "Email is required"
    if not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", email):
        return False, "Invalid email format"
    return True, ""

def validate_phone(phone):
    if not phone:
        return False, "Phone number is required"
    clean_phone = re.sub(r'[\\s\\-\\(\\)]', '', phone)
    if not re.match(r'^[0-9]{10}$', clean_phone):
        return False, "Phone must be 10 digits"
    return True, ""

def validate_age(age):
    if age <= 0:
        return False, "Age must be greater than 0"
    if age > 120:
        return False, "Age must be less than 120"
    return True, ""

def validate_date(check_date):
    if not check_date:
        return False, "Date is required"
    if check_date > date.today():
        return False, "Date cannot be in the future"
    return True, ""

def get_validation_status():
    """Get detailed validation status for all fields"""
    validation_messages = []

    # Check each field and collect validation messages
    if not st.session_state.get('first_name_valid', False):
        if st.session_state.get('first_name', ''):
            is_valid, message = validate_name(st.session_state.first_name, "First name")
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("First name is required")

    if not st.session_state.get('last_name_valid', False):
        if st.session_state.get('last_name', ''):
            is_valid, message = validate_name(st.session_state.last_name, "Last name")
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Last name is required")

    if not st.session_state.get('email_valid', False):
        if st.session_state.get('email', ''):
            is_valid, message = validate_email(st.session_state.email)
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Email is required")

    if not st.session_state.get('phone_valid', False):
        if st.session_state.get('phone', ''):
            is_valid, message = validate_phone(st.session_state.phone)
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Phone number is required")

    if not st.session_state.get('age_valid', False):
        if st.session_state.get('age', 0) > 0:
            is_valid, message = validate_age(st.session_state.age)
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Age is required")

    if not st.session_state.get('birth_date_valid', False):
        if st.session_state.get('birth_date'):
            is_valid, message = validate_date(st.session_state.birth_date)
            if not is_valid:
                validation_messages.append(message)
        else:
            validation_messages.append("Birth date is required")

    # Check date range
    start_date = st.session_state.get('start_date')
    end_date = st.session_state.get('end_date')
    if start_date and end_date and start_date >= end_date:
        validation_messages.append("Start date must be before end date")

    return validation_messages

def update_sidebar_table():
    st.sidebar.title("?? Form Data")

    # Get current values from session state
    current_data = {
        'First Name': st.session_state.get('first_name', ''),
        'Last Name': st.session_state.get('last_name', ''),
        'Email': st.session_state.get('email', ''),
        'Phone': st.session_state.get('phone', ''),
        'Age': st.session_state.get('age', 0),
        'Birth Date': st.session_state.get('birth_date', date.today()),
        'Start Date': st.session_state.get('start_date', date.today()),
        'End Date': st.session_state.get('end_date', date.today())
    }

    # Create markdown table
    table_header = "| Field | Value | Status |\\n|--------|--------|--------|\\n"
    table_rows = ""

    for field, value in current_data.items():
        # Format value based on type
        if isinstance(value, date):
            formatted_value = value.strftime('%Y-%m-%d')
        elif isinstance(value, (int, float)):
            formatted_value = str(value) if value != 0 else ''
        else:
            formatted_value = str(value)

        # Determine validation status
        field_key = field.lower().replace(' ', '_')
        if field in ['Start Date', 'End Date']:
            start_date = st.session_state.get('start_date')
            end_date = st.session_state.get('end_date')
            is_valid = start_date and end_date and start_date < end_date
        else:
            is_valid = st.session_state.get(f'{field_key}_valid', False)

        # Add status emoji
        status = "?" if is_valid and formatted_value else "?" if formatted_value else "?"

        table_rows += f"| {field} | {formatted_value} | {status} |\\n"

    st.sidebar.markdown(table_header + table_rows)

    if st.session_state.get('form_submitted', False):
        st.sidebar.success("Form submitted successfully!")

    if st.sidebar.button("Clear Form"):
        for key in st.session_state.keys():
            del st.session_state[key]
        st.rerun()

def main():
    st.set_page_config(
        page_title="Real-time Form Validation",
        page_icon="?",
        layout="wide"
    )

    st.title("? Real-time Form Validation")

    # Initialize session state
    if 'form_submitted' not in st.session_state:
        st.session_state.form_submitted = False

    # Initialize validation summary
    if 'validation_messages' not in st.session_state:
        st.session_state.validation_messages = []

    # Create columns for the form and validation messages
    form_col, validation_col = st.columns([2, 1])

    with form_col:
        # Personal Information
        st.header("?? Personal Information")

        # First Name (with real-time validation)
        first_name = st.text_input("First Name", key="first_name")
        if first_name:  # Only validate if there's input
            is_valid, message = validate_name(first_name, "First name")
            st.session_state.first_name_valid = is_valid
            if not is_valid:
                st.error(message)
            else:
                st.success("Valid first name!")
        else:
            st.session_state.first_name_valid = False

        # Last Name
        last_name = st.text_input("Last Name", key="last_name")
        if last_name:
            is_valid, message = validate_name(last_name, "Last name")
            st.session_state.last_name_valid = is_valid
            if not is_valid:
                st.error(message)
            else:
                st.success("Valid last name!")
        else:
            st.session_state.last_name_valid = False

        # Contact Information
        st.header("?? Contact Information")

        # Email
        email = st.text_input("Email", key="email")
        if email:
            is_valid, message = validate_email(email)
            st.session_state.email_valid = is_valid
            if not is_valid:
                st.error(message)
            else:
                st.success("Valid email!")
        else:
            st.session_state.email_valid = False

        # Phone
        phone = st.text_input("Phone", help="Format: (123) 456-7890 or 123-456-7890", key="phone")
        if phone:
            is_valid, message = validate_phone(phone)
            st.session_state.phone_valid = is_valid
            if not is_valid:
                st.error(message)
            else:
                st.success("Valid phone number!")
        else:
            st.session_state.phone_valid = False

        # Personal Details
        st.header("?? Personal Details")
        col1, col2 = st.columns(2)

        with col1:
            age = st.number_input("Age", min_value=0, max_value=120, key="age")
            if age > 0:
                is_valid, message = validate_age(age)
                st.session_state.age_valid = is_valid
                if not is_valid:
                    st.error(message)
                else:
                    st.success("Valid age!")
            else:
                st.session_state.age_valid = False

        with col2:
            birth_date = st.date_input("Birth Date", key="birth_date")
            if birth_date:
                is_valid, message = validate_date(birth_date)
                st.session_state.birth_date_valid = is_valid
                if not is_valid:
                    st.error(message)
                else:
                    st.success("Valid birth date!")
            else:
                st.session_state.birth_date_valid = False

        # Schedule
        st.header("?? Schedule")
        col3, col4 = st.columns(2)

        with col3:
            start_date = st.date_input("Start Date", key="start_date")
        with col4:
            end_date = st.date_input("End Date", key="end_date")

        if start_date and end_date:
            if start_date >= end_date:
                st.error("Start date must be before end date")
                st.session_state.start_date_valid = False
                st.session_state.end_date_valid = False
            else:
                st.success("Valid date range!")
                st.session_state.start_date_valid = True
                st.session_state.end_date_valid = True

        # Submit button
        if st.button("Submit"):
            # Get validation messages
            validation_messages = get_validation_status()

            if not validation_messages:
                st.success("Form submitted successfully!")
                st.session_state.form_submitted = True
            else:
                st.error("Please fix the following validation errors:")
                for message in validation_messages:
                    st.error(f"? {message}")

    # Update sidebar with current form data
    update_sidebar_table()

    # Display validation summary in the validation column
    with validation_col:
        st.header("Validation Summary")
        all_valid = not get_validation_status()
        if all_valid:
            st.success("All fields are valid! ?")
        else:
            messages = get_validation_status()
            st.error("Fields needing attention:")
            for message in messages:
                st.warning(f"? {message}")

if __name__ == "__main__":
    main()
        

You can find this code listing at this github repo.


Running the Application

To run the application, use the following command:

streamlit run form_validation2.py

        

Open the provided local URL in your web browser to interact with the app.


Key Takeaways

  • Immediate Feedback: Real-time validation enhances user experience, and it is contextually appealing.
  • Session State: Crucial for maintaining validation status across interactions.
  • User-Friendly UI: Clear messages and visual indicators guide users effectively.
  • Modular Code: Separating validation logic improves maintainability.


Rick and Chris paused on a ridge overlooking a serene canyon.

Rick: "Thanks, Chris. With real-time validation, my app will be as smooth as this trail."

Chris: Smiles "Happy to help. Just remember, whether it's hiking or coding, it's all about the journey and the little adjustments along the way."

Rick: "Well said. Now, how about we let the dogs lead the way back?"

Chris: "Sounds like a plan. Maybe they'll find a shortcut!"

They laughed, resuming their hike with a renewed sense of accomplishment and the satisfaction of a day well spent.


Glossary

Term/Concept Definition Related Streamlit Component Usage Example Real-time Form Validation Immediate feedback on user input as it's entered st.text_input, st.number_input, st.date_input Validating email format as user types Session State Mechanism to store and persist data across reruns st.session_state Storing validation status of form fields Validation Functions Custom functions to check input validity N/A (Python functions) validate_email(), validate_phone() UI Feedback Visual cues for input status st.success(), st.error() Displaying green checkmark for valid input Form Layout Arrangement of input fields in the app st.columns() Creating two-column layout for related fields Sidebar Table Dynamic display of form data st.sidebar.markdown() Showing current form state in sidebar Validation Summary Overview of all validation errors st.error() Listing all invalid fields at form submission


To see this article with syntax highlighting for code listings, diagrams, and images go to my website and see Article Streamlit Part 4: Form Validation Part 2. Also, this glossary table renders correctly there.




Related Links


About the Author

Rick Hightower is a seasoned software engineer and technology enthusiast with a passion for exploring cutting-edge tools in data science and web development. With years of experience in the tech industry, Rick enjoys simplifying complex processes and making technology accessible to developers of all skill levels.

An advocate for continuous learning, Rick shares his knowledge through articles and tutorials, helping others navigate the ever-evolving landscape of software development. His exploration of real-time form validation in Streamlit demonstrates his commitment to empowering developers to create robust and user-friendly applications.

When not coding or writing, Rick can be found hiking the trails of Austin with his dog, blending his love for nature with technology, or engaging in thought-provoking conversations about the future of tech with fellow enthusiasts.



Happy coding with Streamlit! If you have any questions or need more help, feel free to explore the official Streamlit documentation or reach out to the community.

要查看或添加评论,请登录