One place for hosting & domains

      Forms

      How To Use and Validate Web Forms with Flask-WTF


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Web forms, such as text fields and text areas, give users the ability to send data to your application, whether that’s a drop-down or a radio button that the application will use to perform an action, or to send large areas of text to be processed or displayed. For example, in a social media application, you might give users a box where they can add new content to their pages.

      Flask is a lightweight Python web framework that provides useful tools and features for creating web applications in the Python Language. To render and validate web forms in a safe and flexible way in Flask, you’ll use Flask-WTF, which is a Flask extension that helps you use the WTForms library in your Flask application.

      WTForms is a Python library that provides flexible web form rendering. You can use it to render text fields, text areas, password fields, radio buttons, and others. WTForms also provides powerful data validation using different validators, which validate that the data the user submits meets certain criteria you define. For example, if you have a required field, you can ensure data the user submits is provided, or has a certain length.

      WTForms also uses a CSRF token to provide protection from CSRF attacks, which are attacks that allows the attacker to execute unwanted actions on a web application in which the user is authenticated. A successful CSRF attack can force the user to perform state-changing requests like transferring funds to the attacker’s bank account in a banking application, changing the user’s email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.

      In this tutorial, you’ll build a small web application that demonstrates how to render and validate web forms using Flask-WTF. The application will have a page for displaying courses that are stored in a Python list, and the index page will have a form for entering the course title, its description, price, availability, and level (beginner, intermediate, or advanced).

      Prerequisites

      Step 1 — Installing Flask and Flask-WTF

      In this step, you’ll install Flask and Flask-WTF, which also installs the WTForms library automatically.

      With your virtual environment activated, use pip to install Flask and Flask-WTF:

      • pip install Flask Flask-WTF

      Once the installation is successfully finished, you’ll see a line similar to the following at the end of the output:

      Output

      Successfully installed Flask-2.0.2 Flask-WTF-1.0.0 Jinja2-3.0.3 MarkupSafe-2.0.1 WTForms-3.0.0 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1

      As you can see, the WTForms library was also installed as a dependency of the Flask-WTF package. The rest of the packages are Flask dependencies.

      Now that you’ve installed the required Python packages, you’ll set up a web form next.

      Step 2 — Setting up Forms

      In this step, you’ll set up a web form using fields and validators you’ll import from the WTForms library.

      You’ll set up the following fields:

      • Title: A text input field for the course title.
      • Description: A text area field for the course description.
      • Price: An integer field for the price of the course.
      • Level: A radio field for the course level with three choices: Beginner, Intermediate, and Advanced.
      • Available: A checkbox field that indicates whether the course is currently available.

      First, open a new file called forms.py in your flask_app directory. This file will have the forms you’ll need in your application:

      This file will have a class that represents your web form. Add the following imports at the top:

      flask_app/forms.py

      from flask_wtf import FlaskForm
      from wtforms import (StringField, TextAreaField, IntegerField, BooleanField,
                           RadioField)
      from wtforms.validators import InputRequired, Length
      
      

      To build a web form, you will create a subclass of the FlaskForm base class, which you import from the flask_wtf package. You also need to specify the fields you use in your form, which you will import from the wtforms package.

      You import the following fields from the WTForms library:

      In the line from wtforms.validators import InputRequired, Length, you import validators to use on the fields to make sure the user submits valid data. InputRequired is a validator you’ll use to ensure the input is provided, and Length is for validating the length of a string to ensure it has a minimum number of characters, or that it doesn’t exceed a certain length.

      Next, add the following class after the import statements:

      flask_app/forms.py

      
      class CourseForm(FlaskForm):
          title = StringField('Title', validators=[InputRequired(),
                                                   Length(min=10, max=100)])
          description = TextAreaField('Course Description',
                                      validators=[InputRequired(),
                                                  Length(max=200)])
          price = IntegerField('Price', validators=[InputRequired()])
          level = RadioField('Level',
                             choices=['Beginner', 'Intermediate', 'Advanced'],
                             validators=[InputRequired()])
          available = BooleanField('Available', default="checked")
      

      Save and close the file.

      In this CourseForm class, you inherit from the FlaskForm base class you imported earlier. You define a collection of form fields as class variables using the form fields you imported from the WTForms library. When you instantiate a field, the first argument is the field’s label.

      You define the validators for each field by passing a list of the validators you import from the wtforms.validators module. The title field, for example, has the string 'Title' as a label, and two validators:

      • InputRequired: To indicate that the field should not be empty.
      • Length: Takes two arguments; min is set to 10 to make sure that the title is at least 10 characters long, and max is set to 100 to ensure it doesn’t exceed 100 characters.

      The description text area field has an InputRequired validator and a Length validator with the max parameter set to 200, with no value for the min parameter, which means the only requirement is that it doesn’t exceed 200 characters.

      Similarly you define a required integer field for the price of the course called price.

      The level field is a radio field with multiple choices. You define the choices in a Python list and pass it to the choices parameter. You also define the field as required using the InputRequired validator.

      The available field is a check box field. You set a default 'checked' value by passing it to the default parameter. This means the check box will be checked when adding new courses unless the user unchecks it, meaning courses are available by default.

      For more on how to use the WTForms library, see the Crash Course page on the WTForms documentation. See the Fields page for more fields, and the Validators page for more validators to validate form data.

      You’ve configured your web form in a forms.py file. Next, you’ll create a Flask application, import this form, and display its fields on the index page. You’ll also display a list of courses on another page.

      Step 3 — Displaying the Web Form and Courses

      In this step, you’ll create a Flask application, display the web form you created in the previous step on the index page, and also create a list of courses and a page for displaying the courses on it.

      With your programming environment activated and Flask installed, open a file called app.py for editing inside your flask_app directory:

      This file will import the necessary class and helpers from Flask, and the CourseForm from the forms.py file. You’ll build a list of courses, then instantiate the form and pass it to a template file. Add the following code to app.py:

      flask_app/app.py

      from flask import Flask, render_template, redirect, url_for
      from forms import CourseForm
      
      app = Flask(__name__)
      app.config['SECRET_KEY'] = "https://www.digitalocean.com/community/tutorials/your secret key'
      
      
      courses_list = [{
          'title': 'Python 101',
          'description': 'Learn Python basics',
          'price': 34,
          'available': True,
          'level': 'Beginner'
          }]
      
      
      @app.route('/', methods=('GET', 'POST'))
      def index():
          form = CourseForm()
          return render_template('index.html', form=form)
      

      Save and close the file.

      Here you import the following from Flask:

      • The Flask class to create a Flask application instance.
      • The render_template() function to render the index template.
      • The redirect() function to redirect the user to the courses page once a new course is added.
      • The url_for() function for building URLs.

      First you import the CourseForm() class from the forms.py file, then you create a Flask application instance called app.

      You set up a secret key configuration for WTForms to use when generating a CSRF token to secure your web forms. The secret key should be a long random string. See Step 3 of How To Use Web Forms in a Flask Application for more information on how to obtain a secret key.

      Then you create a list of dictionaries called courses_list, which currently has one dictionary with a sample course titled 'Python 101'. Here, you use a Python list as a data store for demonstration purposes. In a real world scenario, you’ll use a database such as SQLite. See How To Use an SQLite Database in a Flask Application to learn how to use a database to store your courses’ data.

      You create a / main route using the app.route() decorator on the index() view function. It accepts both GET and POST HTTP methods in the methods parameter. GET methods are for retrieving data, and POST requests are for sending data to the server, through a web form for example. For more, see How To Use Web Forms in a Flask Application.

      You instantiate the CourseForm() class that represents the web form and save the instance in a variable called form. You then return a call to the render_template() function, passing it a template file called index.html and the form instance.

      To display the web form on the index page, you will first create a base template, which will have all the basic HTML code other templates will also use to avoid code repetition. Then you’ll create the index.html template file you rendered in your index() function. To learn more about templates, see How to Use Templates in a Flask Application.

      Create a templates folder in your flask_app directory where Flask searches for templates, then open a template file called base.html, which will be the base template for other templates:

      • mkdir templates
      • nano templates/base.html

      Add the following code inside the base.html file to create the base template with a navbar and a content block:

      flask_app/templates/base.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for('index') }}">FlaskApp</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      This base template has all the HTML boilerplate you’ll need to reuse in your other templates. The title block will be replaced to set a title for each page, and the content block will be replaced with the content of each page. The navigation bar has two links, one for the index page where you use the url_for() helper function to link to the index() view function, and the other for an About page if you choose to include one in your application.

      Save and close the file.

      Next, open a template called index.html. This is the template you referenced in the app.py file:

      • nano templates/index.html

      This file will have the web form you passed to the index.html template via the form variable. Add the following code to it:

      flask_app/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Add a New Course {% endblock %}</h1>
      
          <form method="POST" action="/">
              {{ form.csrf_token }}
              <p>
                  {{ form.title.label }}
                  {{ form.title(size=20) }}
              </p>
      
              {% if form.title.errors %}
                  <ul class="errors">
                      {% for error in form.title.errors %}
                          <li>{{ error }}</li>
                      {% endfor %}
                  </ul>
              {% endif %}
      
              <p>
                  {{ form.description.label }}
              </p>
              {{ form.description(rows=10, cols=50) }}
      
              {% if form.description.errors %}
                  <ul class="errors">
                      {% for error in form.description.errors %}
                          <li>{{ error }}</li>
                      {% endfor %}
                  </ul>
              {% endif %}
      
              <p>
                  {{ form.price.label }}
                  {{ form.price() }}
              </p>
      
              {% if form.price.errors %}
                  <ul class="errors">
                      {% for error in form.price.errors %}
                          <li>{{ error }}</li>
                      {% endfor %}
                  </ul>
              {% endif %}
      
              <p>
                  {{ form.available() }} {{ form.available.label }}
              </p>
      
              {% if form.available.errors %}
                  <ul class="errors">
                      {% for error in form.available.errors %}
                          <li>{{ error }}</li>
                      {% endfor %}
                  </ul>
              {% endif %}
      
              <p>
                  {{ form.level.label }}
                  {{ form.level() }}
              </p>
      
              {% if form.level.errors %}
                  <ul class="errors">
                      {% for error in form.level.errors %}
                          <li>{{ error }}</li>
                      {% endfor %}
                  </ul>
              {% endif %}
      
              <p>
                  <input type="submit" value="Add">
              </p>
          </form>
      
      {% endblock %}
      

      Save and close the file.

      You extend the base template, and set a title in an <h1> tag. Then you render the web form fields inside a <form> tag, setting its method to POST and the action to the / main route, which is the index page. You first render the CSRF token WTForms uses to protect your form from CSRF attacks using the line {{ form.csrf_token }}. This token gets sent to the server with the rest of the form data. Remember to always render this token to secure your forms.

      You render each field using the syntax form.field() and you render its label using the syntax form.field.label. You can pass arguments to the field to control how it is displayed. For example, you set the size of the title input field in {{ form.title(size=20) }}, and you set the numbers of rows and columns for the description text area via the parameters rows and cols the same way you would do normally in HTML. You can use the same method to pass additional HTML attributes to a field such as the class attribute to set a CSS class.

      You check for validation errors using the syntax if form.field.errors. If a field has errors, you loop through them with a for loop and display them in a list below the field.

      While in your flask_app directory with your virtual environment activated, tell Flask about the application (app.py in this case) using the FLASK_APP environment variable. Then set the FLASK_ENV environment variable to development to run the application in development mode and get access to the debugger. For more information about the Flask debugger, see How To Handle Errors in a Flask Application. Use the following commands to do this (on Windows, use set instead of export):

      • export FLASK_APP=app
      • export FLASK_ENV=development

      Next, run the application:

      With the development server running, visit the following URL using your browser:

      http://127.0.0.1:5000/
      

      You’ll see the web form displayed on the index page:

      Index Page

      Try to submit the form without filling in the title. You’ll see an error message informing you that the title is required. Experiment with the form by submitting invalid data (such as a short title less than 10 characters long, or a description over 200 characters long) to see other error messages.

      Filling the form with valid data does nothing so far because you don’t have code that handles form submission. You’ll add the code for that later.

      For now, you need a page to display the courses you have in your list. Later, handling the web form data will add a new course to the list and redirect the user to the courses page to see the new course added to it.

      Leave the development server running and open another terminal window.

      Next, open app.py to add the courses route:

      Add the following route at the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/courses/')
      def courses():
          return render_template('courses.html', courses_list=courses_list)
      

      Save and close the file.

      This route renders a template called courses.html, passing it the courses_list list.

      Then create the courses.html template to display courses:

      • nano templates/courses.html

      Add the following code to it:

      flask_app/templates/courses.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Courses {% endblock %}</h1>
          <hr>
          {% for course in courses_list %}
              <h2> {{ course['title'] }} </h2>
              <h4> {{ course['description'] }} </h4>
              <p> {{ course['price'] }}$ </p>
              <p><i>({{ course['level'] }})</i></p>
              <p>Availability:
                  {% if course['available'] %}
                      Available
                  {% else %}
                      Not Available
                  {% endif %}</p>
              <hr>
          {% endfor %}
      {% endblock %}
      

      Save and close the file.

      You set a title and loop through the items of the courses_list list. You display the title in an <h2> tag, the description in an <h4> tag, and the price and course level in a <p> tag.
      You check whether the course is available using the condition if course['available']. You display the text “Available” if the course is available, and the text “Not Available” if it’s not available.

      Use your browser to go to the courses page:

      http://127.0.0.1:5000/courses/
      

      You’ll see a page with one course displayed, because you only have one course in your course list so far:

      Courses Page

      Next, open base.html to add a link to the courses page in the navigation bar:

      Edit it to look as follows:

      flask_app/templates/base.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("index') }}">FlaskApp</a>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("courses') }}">Courses</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Save and close the file.

      Refresh the index page, and you’ll see a new Courses link in the navigation bar.

      You’ve created the pages you need for your application: An index page with a web form for adding new courses and a page for displaying the courses you have in your list.

      To make the application functional, you need to handle the web form data when the user submits it by validating it and adding it to the courses list. You’ll do this next.

      Step 4 — Accessing Form Data

      In this step, you’ll access data the user submits, validate it, and add it to the list of courses.

      Open app.py to add code for handling the web form data inside the index() function:

      Edit the index() function to look as follows:

      flask_app/app.py

      # ...
      @app.route('/', methods=('GET', 'POST'))
      def index():
          form = CourseForm()
          if form.validate_on_submit():
              courses_list.append({'title': form.title.data,
                                   'description': form.description.data,
                                   'price': form.price.data,
                                   'available': form.available.data,
                                   'level': form.level.data
                                   })
              return redirect(url_for('courses'))
          return render_template('index.html', form=form)
      

      Save and close the file.
      Here, you call the validate_on_submit() method on the form object, which checks that the request is a POST request, and runs the validators you configured for each field. If at least one validator returns an error, the condition will be False, and each error will be displayed below the field that caused it.

      If the submitted form data is valid, the condition is True, and the code below the if statement will be executed. You build a course dictionary, and use the append method to add the new course to the courses_list list. You access the value of each field using the syntax form.field.data. After you add the new course dictionary to the courses list, you redirect the user to the Courses page.

      With the development server running, visit the index page:

      http://127.0.0.1:5000/
      

      Fill the form with valid data and submit it. You’ll be redirected to the Courses page, and you’ll see the new course displayed on it.

      Conclusion

      You made a Flask application that has a web form you built using the Flask-WTF extension and the WTForms library. The form has several types of fields to receive data from the user, validate it using special WTForms validators, and add it to a data store.

      If you would like to read more about Flask, check out the other tutorials in the How To Create Web Sites with Flask series.



      Source link

      How To Use Web Forms in a Flask Application


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Web forms, such as text fields and text areas, give users the ability to send data to your application to use it to perform an action, or to send larger areas of text to the application. For example, in a social media application, you might give users a box where they can add new content to their pages. Another example is a login page, where you would give the user a text field to enter their username and a password field to enter their password. The server (your Flask application in this case) uses the data the user submits and either signs them in if the data is valid, or responds with a message like Invalid credentials! to inform the user that the data they submitted is not correct.

      Flask is a lightweight Python web framework that provides useful tools and features for creating web applications in the Python Language. In this tutorial, you’ll build a small web application that demonstrates how to use web forms. The application will have a page for displaying messages that are stored in a Python list, and a page for adding new messages. You’ll also use message flashing to inform users of an error when they submit invalid data.

      Prerequisites

      Step 1 — Displaying Messages

      In this step, you’ll create a Flask application with an index page for displaying messages that are stored in a list of Python dictionaries.

      First open a new file called app.py for editing:

      Add the following code inside the app.py file to create a Flask server with a single route:

      flask_app/app.py

      from flask import Flask, render_template
      
      app = Flask(__name__)
      
      messages = [{'title': 'Message One',
                   'content': 'Message One Content'},
                  {'title': 'Message Two',
                   'content': 'Message Two Content'}
                  ]
      
      @app.route('/')
      def index():
          return render_template('index.html', messages=messages)
      

      Save and close the file.

      In this file, you first import the Flask class and the render_template() function from the flask package. You then use the Flask class to create a new application instance called app, passing the special __name__ variable, which is needed for Flask to set up some paths behind the scenes. Rendering templates is covered in the tutorial How To Use Templates in a Flask Application.

      You then create a global Python list called messages, which has Python dictionaries inside it. Each dictionary has two keys: title for the title of the message, and content for the message content. This is a simplified example of a data storage method; in a real-world scenario, you’d use a database that permanently saves the data and allows you to manipulate it more efficiently.

      After creating the Python list, you use the @app.route() decorator to create a view function called index(). In it, you return a call to the render_template() function, which indicates to Flask that the route should display an HTML template. You name this template index.html (you’ll create it later), and you pass a variable called messages to it. This variable holds the messages list you previously declared as a value and makes it available to the HTML template. View functions are covered in the tutorial How To Create Your First Web Application Using Flask and Python 3.

      Next, create a templates folder in your flask_app directory where Flask searches for templates, then open a template file called base.html, which will have code that other templates will inherit to avoid code repetition:

      • mkdir templates
      • nano templates/base.html

      Add the following code inside the base.html file to create the base template with a navbar and a content block:

      flask_app/templates/base.html

      
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              .message {
                  padding: 10px;
                  margin: 5px;
                  background-color: #f3f3f3
              }
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
      
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for('index') }}">FlaskApp</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Save and close the file.

      This base template has all the HTML boilerplate you’ll need to reuse in your other templates. The title block will be replaced to set a title for each page, and the content block will be replaced with the content of each page. The navigation bar has two links, one for the index page where you use the url_for() helper function to link to the index() view function, and the other for an About page if you choose to include one in your application.

      Next, open a template called index.html. This is the template you referenced in the app.py file:

      • nano templates/index.html

      Add the following code to it:

      flask_app/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Messages {% endblock %}</h1>
          {% for message in messages %}
              <div class="message">
                  <h3>{{ message['title'] }}</h3>
                  <p>{{ message['content'] }}</p>
              </div>
          {% endfor %}
      {% endblock %}
      

      Save and close the file.

      In this code, you extend the base.html template and replace the contents of the content block. You use an <h1> heading that also serves as a title.

      You use a Jinja for loop in the line {% for message in messages %} to go through each message in the messages list. You use a <div> tag to contain the message’s title and content. You display the title in an <h3> heading and the content in a <p> tag.

      While in your flask_app directory with your virtual environment activated, tell Flask about the application (app.py in this case) using the FLASK_APP environment variable:

      Then set the FLASK_ENV environment variable to development to run the application in development mode and get access to the debugger. For more information about the Flask debugger, see How To Handle Errors in a Flask Application. Use the following commands to do this (on Windows, use set instead of export):

      • export FLASK_ENV=development

      Next, run the application:

      With the development server running, visit the following URL using your browser:

      http://127.0.0.1:5000/
      

      You’ll see the messages in the messages list displayed on the index page:

      Index Page

      Now that you have set up your web application and displayed the messages, you’ll need a way to allow users to add new messages to the index page. This is done through web forms, which you’ll set up in the next step.

      Step 2 — Setting Up Forms

      In this step, you will create a page in your application that allows users to add new messages into the list of messages via a web form.

      Leave the development server running and open a new terminal window.

      First, open your app.py file:

      Add the following route to the end of the file:

      flask_app/app.py

      # ...
      
      @app.route('/create/', methods=('GET', 'POST'))
      def create():
          return render_template('create.html')
      

      Save and close the file.

      This /create route has the methods parameter with the tuple ('GET', 'POST') to accept both GET and POST requests. GET and POST are HTTP methods. By default, only GET requests are accepted, which are used to retrieve data, such as asking the server for an index page or an About page. POST requests are used to submit data to a specific route, which often changes the data on the server.

      In this example, you will ask for the create page using a GET request. The Create page will have a web form with input fields and a Submit button. When a user fills in the web form and clicks the Submit button, a POST request gets sent to the /create route. There you handle the request, validate the submitted data to ensure the user has not submitted an empty form, and add it to the messages list.

      The create() view function currently does only one thing: render a template called create.html when it receives a regular GET request. You will now create this template, then edit the function to handle POST requests in the next step.

      Open a new template file called create.html:

      • nano templates/create.html

      Add the following code to it:

      flask_app/templates/create.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Add a New Message {% endblock %}</h1>
          <form method="post">
              <label for="title">Title</label>
              <br>
              <input type="text" name="title"
                     placeholder="Message title"
                     value="https://www.digitalocean.com/community/tutorials/{{ request.form['title'] }}"></input>
              <br>
      
              <label for="content">Message Content</label>
              <br>
              <textarea name="content"
                        placeholder="Message content"
                        rows="15"
                        cols="60"
                        >{{ request.form['content'] }}</textarea>
              <br>
              <button type="submit">Submit</button>
          </form>
      {% endblock %}
      
      

      Save and close the file.

      In this code, you extend the base.html template and replace the content block with an <h1> heading that serves as a title for the page. In the <form> tag, you set the method attribute to post so the form data gets sent to the server as a POST request.

      In the form, you have a text input field named title; this is the name you’ll use on the application to access the title form data. You give the <input> tag a value of {{ request.form['title'] }}. This is useful to restore the data the user enters so it does not get lost when things go wrong. For example, if the user forgets to fill in the required content text area, a request gets sent to the server and an error message will come back as a response, but the data in the title will not be lost because it will be saved on the request global object, and can be accessed via request.form['title'].

      After the title input field, you add a text area named content with the value {{ request.form['content'] }} for the same reasons mentioned previously.

      Last, you have a Submit button at the end of the form.

      Now, with the development server running, use your browser to navigate to the /create route:

      http://127.0.0.1:5000/create
      

      You will see an “Add a New Message” page with an input field for a message title, a text area for the message’s content, and a Submit button.

      Add a new message

      This form submits a POST request to your create() view function. However, there is no code to handle a POST request in the function yet, so nothing happens after filling in the form and submitting it. In the next step, you’ll handle the incoming POST request when a form is submitted. You’ll check whether the submitted data is valid (not empty), and add the message title and content to the messages list.

      Step 3 — Handling Form Requests

      In this step, you will handle form requests on the application side. You’ll access the form data the user submits via the form you created in the previous step and add it to the list of messages. You’ll also use message flashing to inform users when they submit invalid data. The flash message will only be shown once and will disappear on the next request (if you navigate to another page for example).

      Open the app.py file for editing:

      First, you’ll import the following from the Flask framework:

      • The global request object to access incoming request data that will be submitted via the HTML form you built in the last step.
      • The url_for() function to generate URLs.
      • The flash() function to flash a message when a request is processed (to inform the user that everything went well, or to inform them of an issue if the submitted data is not valid).
      • The redirect() function to redirect the client to a different location.

      Add these imports to the first line in the file:

      flask_app/app.py

      from flask import Flask, render_template, request, url_for, flash, redirect
      
      # ...
      

      The flash() function stores flashed messages in the client’s browser session, which requires setting a secret key. This secret key is used to secure sessions, which allow Flask to remember information from one request to another, such as moving from the new message page to the index page. The user can access the information stored in the session, but cannot modify it unless they have the secret key, so you must never allow anyone to access your secret key. See the Flask documentation for sessions for more information.

      The secret key should be a long random string. You can generate a secret key using the os module with the os.urandom() method, which returns a string of random bytes suitable for cryptographic use. To get a random string using it, open a new terminal and open the Python interactive shell using the following command:

      In the Python interactive shell, import the os module from the standard library and call the os.urandom() method as follows:

      • import os
      • os.urandom(24).hex()

      You’ll get a string similar to the following:

      Output

      'df0331cefc6c2b9a5d0208a726a5d1c0fd37324feba25506'

      You can use the string you get as your secret key.

      To set the secret key, add a SECRET_KEY configuration to your application via the app.config object. Add it directly following the app definition before defining the messages variable:

      flask_app/app.py

      
      # ...
      app = Flask(__name__)
      app.config['SECRET_KEY'] = 'your secret key'
      
      
      messages = [{'title': 'Message One',
                   'content': 'Message One Content'},
                  {'title': 'Message Two',
                   'content': 'Message Two Content'}
                  ]
      # ...
      

      Next, modify the create() view function to look exactly as follows:

      flask_app/app.py

      # ...
      
      @app.route('/create/', methods=('GET', 'POST'))
      def create():
          if request.method == 'POST':
              title = request.form['title']
              content = request.form['content']
      
              if not title:
                  flash('Title is required!')
              elif not content:
                  flash('Content is required!')
              else:
                  messages.append({'title': title, 'content': content})
                  return redirect(url_for('index'))
      
          return render_template('create.html')
      

      In the if statement you ensure that the code following it is only executed when the request is a POST request via the comparison request.method == 'POST'.

      You then extract the submitted title and content from the request.form object that gives you access to the form data in the request. If the title is not provided, the condition if not title would be fulfilled. In that case, you display a message to the user informing them that the title is required using the flash() function. This adds the message to a flashed messages list. You will later display these messages on the page as part of the base.html template. Similarly, if the content is not provided, the condition elif not content will be fulfilled. If so, you add the 'Content is required!' message to the list of flashed messages.

      If the title and the content of the message are properly submitted, you use the line messages.append({'title': title, 'content': content}) to add a new dictionary to the messages list, with the title and content the user provided. Then you use the redirect() function to redirect users to the index page. You use the url_for() function to link to the index page.

      Save and close the file.

      Now, navigate to the /create route using your web browser:

      http://127.0.0.1:5000/create
      

      Fill in the form with a title of your choice and some content. Once you submit the form, you will see the new message listed on the index page.

      Lastly, you’ll display flashed messages and add a link for the “New Message” page to the navigation bar in the base.html template to have easy access to this new page. Open the base template file:

      Edit the file by adding a new <a> tag after the FlaskApp link in the navigation bar inside the <nav> tag. Then add a new for loop directly above the content block to display the flashed messages below the navigation bar. These messages are available in the special get_flashed_messages() function Flask provides. Then add a class attribute called alert to each message and give it some CSS properties inside the <style> tag:

      flask_app/templates/base.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>{% block title %} {% endblock %} - FlaskApp</title>
          <style>
              .message {
                  padding: 10px;
                  margin: 5px;
                  background-color: #f3f3f3
              }
              nav a {
                  color: #d64161;
                  font-size: 3em;
                  margin-left: 50px;
                  text-decoration: none;
              }
      
              .alert {
                  padding: 20px;
                  margin: 5px;
                  color: #970020;
                  background-color: #ffd5de;
              }
      
          </style>
      </head>
      <body>
          <nav>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("index') }}">FlaskApp</a>
              <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("create') }}">Create</a>
              <a href="#">About</a>
          </nav>
          <hr>
          <div class="content">
              {% for message in get_flashed_messages() %}
                  <div class="alert">{{ message }}</div>
              {% endfor %}
              {% block content %} {% endblock %}
          </div>
      </body>
      </html>
      

      Save and close the file, and then reload https://127.0.0.1:5000 in your browser. The navigation bar will now have a “Create” item that links to the /create route.

      To see how flash messages work, go to the “Create” page, and click the Submit button without filling the two fields. You’ll receive a message that looks like this:

      No title no content flash message

      Go back to the index page and you’ll see that the flashed messages below the navigation bar disappear, even though they are displayed as part of the base template. If they weren’t flashed messages, they would be displayed on the index page too, because it also inherits from the base template.

      Try submitting the form with a title but no content. You’ll see the message “Content is required!”. Click the FlaskApp link in the navigation bar to go back to the index page, then click the Back button to come back to the Create page. You’ll see that the message content is still there. This only works if you click the Back button, because it saves the previous request. Clicking the Create link in the navigation bar will send a new request, which clears the form, and as a result, the flashed message will disappear.

      You now know how to receive user input, how to validate it, and how to add it to a data source.

      Note:
      The messages you add to the messages list will disappear whenever the server is stopped, because Python lists are only saved in memory, to save your messages permanently, you will need to use a database like SQLite. Check out How To Use the sqlite3 Module in Python 3 to learn how to use SQLite with Python.

      Conclusion

      You created a Flask application where users can add messages to a list of messages displayed on the index page. You created a web form, handled the data the user submits via the form, and added it to your messages list. You also used flash messages to inform the user when they submit invalid data.

      If you would like to read more about Flask, check out the other tutorials in the Flask series.



      Source link

      How To Build Forms in React


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      Forms are a crucial component of React web applications. They allow users to directly input and submit data in components ranging from a login screen to a checkout page. Since most React applications are single page applications (SPAs), or web applications that load a single page through which new data is displayed dynamically, you won’t submit the information directly from the form to a server. Instead, you’ll capture the form information on the client-side and send or display it using additional JavaScript code.

      React forms present a unique challenge because you can either allow the browser to handle most of the form elements and collect data through React change events, or you can use React to fully control the element by setting and updating the input value directly. The first approach is called an uncontrolled component because React is not setting the value. The second approach is called a controlled component because React is actively updating the input.

      In this tutorial, you’ll build forms using React and handle form submissions with an example app that submits requests to buy apples. You’ll also learn the advantages and disadvantages of controlled and uncontrolled components. Finally, you’ll dynamically set form properties to enable and disable fields depending on the form state. By the end of this tutorial, you’ll be able to make a variety of forms using text inputs, checkboxes, select lists, and more.

      Prerequisites

      Step 1 — Creating a Basic Form with JSX

      In this step, you’ll create an empty form with a single element and a submit button using JSX. You’ll handle the form submit event and pass the data to another service. By the end of this step, you’ll have a basic form that will submit data to an asynchronous function.

      To begin, open App.js:

      • nano src/components/App/App.js

      You are going to build a form for purchasing apples. Create a <div> with a className of <wrapper>. Then add an <h1> tag with the text “How About Them Apples” and an empty form element by adding the following highlighted code:

      form-tutorial/src/components/App/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return (
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            <form>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Next, inside the <form> tag, add a <fieldset> element with an <input> element surrounded by a <label> tag. By wrapping the <input> element with a <label> tag, you are aiding screen readers by associating the label with the input. This will increase the accessibility of your application.

      Finally, add a submit <button> at the bottom of the form:

      form-tutorial/src/components/App/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            <form>
            <fieldset>
               <label>
                 <p>Name</p>
                 <input name="name" />
               </label>
             </fieldset>
             <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save and close the file. Then open App.css to set the styling:

      • nano src/components/App/App.css

      Add padding to the .wrapper and margin to the fieldset to give some space between elements:

      form-tutorial/src/components/App/App.css

      .wrapper {
          padding: 5px 20px;
      }
      
      .wrapper fieldset {
          margin: 20px 0;
      }
      

      Save and close the file. When you do, the browser will reload and you’ll see a basic form.

      Basic form with a field for

      If you click on the Submit button, the page will reload. Since you are building a single page application, you will prevent this standard behavior for a button with a type="submit". Instead, you’ll handle the submit event inside the component.

      Open App.js:

      • nano src/components/App/App.js

      To handle the event, you’ll add an event handler to the <form> element, not the <button>. Create a function called handleSubmit that will take the SyntheticEvent as an argument. TheSyntheticEvent is a wrapper around the standard Event object and contains the same interface. Call .preventDefault to stop the page from submitting the form then trigger an alert to show that the form was submitted:

      form-tutorial/src/components/App/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        const handleSubmit = event => {
         event.preventDefault();
         alert('You have submitted the form.')
       }
      
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" />
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do the browser will reload. If you click the submit button, the alert will pop up, but the window will not reload.

      Form submit alert

      In many React applications, you’ll send the data to an external service, like a Web API. When the service resolves, you’ll often show a success message, redirect the user, or do both.

      To simulate an API, add a setTimeout function in the handleSubmit function. This will create an asynchronous operation that waits a certain amount of time before completing, which behaves similarly to a request for external data. Then use the useState Hook to create a submitting variable and a setSubmitting function. Call setSubmitting(true) when the data is submitted and call setSubmitting(false) when the timeout is resolved:

      form-tutorial/src/components/App/App.js

      
      import React, { useState } from 'react';
      import './App.css';
      
      function App() {
        const [submitting, setSubmitting] = useState(false);
        const handleSubmit = event => {
          event.preventDefault();
         setSubmitting(true);
      
         setTimeout(() => {
           setSubmitting(false);
         }, 3000)
       }
      
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
             <div>Submtting Form...</div>
           }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" />
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      In addition, you will alert the user that their form is submitting by displaying a short message in the HTML that will display when submitting is true.

      Save the file. When you do, the browser will reload and you’ll receive the message on submit:

      Form submitting shows message for 3 seconds

      Now you have a basic form that handles the submit event inside the React component. You’ve connected it to your JSX using the onSubmit event handler and you are using Hooks to conditionally display an alert while the handleSubmit event is running.

      In the next step, you’ll add more user inputs and save the data to state as the user fills out the form.

      Step 2 — Collecting Form Data Using Uncontrolled Components

      In this step, you’ll collect form data using uncontrolled components. An uncontrolled component is a component that does not have a value set by React. Instead of setting the data on the component, you’ll connect to the onChange event to collect the user input. As you build the components, you’ll learn how React handles different input types and how to create a reusable function to collect form data into a single object.

      By the end of this step, you’ll be able to build a form using different form elements, including dropdowns and checkboxes. You’ll also be able to collect, submit, and display form data.

      Note: In most cases, you’ll use controlled components for your React application. But it’s a good idea to start with uncontrolled components so that you can avoid subtle bugs or accidental loops that you might introduce when incorrectly setting a value.

      Currently, you have a form that can submit information, but there is nothing to submit. The form has a single <input> element, but you are not collecting or storing the data anywhere in the component. In order to be able to store and process the data when the user submits a form, you’ll need to create a way to manage state. You’ll then need to connect to each input using an event handler.

      Inside App.js, use the useReducer Hook to create a formData object and a setFormData function. For the reducer function, pull the name and value from the event.target object and update the state by spreading the current state while adding the name and value at the end. This will create a state object that preserves the current state while overwriting specific values as they change:

      form-tutorial/src/components/App/App.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      const formReducer = (state, event) => {
       return {
         ...state,
         [event.target.name]: event.target.value
       }
      }
      
      function App() {
        const [formData, setFormData] = useReducer(formReducer, {});
        const [submitting, setSubmitting] = useState(false);
      
        const handleSubmit = event => {
          event.preventDefault();
          setSubmitting(true);
      
          setTimeout(() => {
            setSubmitting(false);
          }, 3000)
        }
      
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
              <div>Submtting Form...</div>
            }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={setFormData}/>
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      After making the reducer, add setFormData to the onChange event handler on the input. Save the file. When you do, the browser will reload. However, if you try and type in the input, you’ll get an error:

      Error with SyntheticEvent

      The problem is that the SyntheticEvent is reused and cannot be passed to an asynchronous function. In other words, you can’t pass the event directly. To fix this, you’ll need to pull out the data you need before calling the reducer function.

      Update the reducer function to take an object with a property of name and value. Then create a function called handleChange that pulls the data from the event.target and passes the object to setFormData. Finally, update the onChange event handler to use the new function:

      form-tutorial/src/components/App/App.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      const formReducer = (state, event) => {<^>
       return {
         ...state,
         [event.name]: event.value
       }
      }
      
      function App() {
        const [formData, setFormData] = useReducer(formReducer, {});
        const [submitting, setSubmitting] = useState(false);
      
        const handleSubmit = event => {
          event.preventDefault();
          setSubmitting(true);
      
          setTimeout(() => {
            setSubmitting(false);
          }, 3000);
        }
      
        const handleChange = event => {
          setFormData({
            name: event.target.name,
            value: event.target.value,
          });
        }
      
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
              <div>Submtting Form...</div>
            }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={handleChange}/>
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do the page will refresh and you’ll be able to enter data.

      Now that you are collecting the form state, update the user display message to show the data in an unordered list (<ul>) element.

      Convert the data to an array using Object.entries, then map over the data converting each member of the array to an <li> element with the name and the value. Be sure to use the name as the key prop for the element:

      form-tutorial/src/components/App/App.js

      ...
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
             <div>
               You are submitting the following:
               <ul>
                 {Object.entries(formData).map(([name, value]) => (
                   <li key={name}><strong>{name}</strong>:{value.toString()}</li>
                 ))}
               </ul>
             </div>
            }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={handleChange}/>
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do the page will reload and you’ll be able to enter and submit data:

      Fill out the form and submit

      Now that you have a basic form, you can add more elements. Create another <fieldset> element and add in a <select> element with different apple varieties for each <option>, an <input> with a type="number" and a step="1" to get a count that increments by 1, and an <input> with a type="checkbox" for a gift wrapping option.

      For each element, add the handleChange function to the onChange event handler:

      form-tutorial/src/components/App/App.js

      ...
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
              <div>
                You are submitting the following:
                <ul>
                  {Object.entries(formData).map(([name, value]) => (
                    <li key={name}><strong>{name}</strong>: {value.toString()}</li>
                  ))}
                </ul>
              </div>
            }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={handleChange}/>
                </label>
              </fieldset>
              <fieldset>
               <label>
                 <p>Apples</p>
                 <select name="apple" onChange={handleChange}>
                     <option value="">--Please choose an option--</option>
                     <option value="fuji">Fuji</option>
                     <option value="jonathan">Jonathan</option>
                     <option value="honey-crisp">Honey Crisp</option>
                 </select>
               </label>
               <label>
                 <p>Count</p>
                 <input type="number" name="count" onChange={handleChange} step="1"/>
               </label>
               <label>
                 <p>Gift Wrap</p>
                 <input type="checkbox" name="gift-wrap" onChange={handleChange} />
               </label>
             </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, the page will reload and you’ll have a variety of input types for your form:

      Form with all input types

      There is one special case here to take into consideration. The value for the gift wrapping checkbox will always be "on", regardless of whether the item is checked or not. Instead of using the event’s value, you’ll need to use the checked property.

      Update the handleChange function to see if the event.target.type is checkbox. If it is, pass the event.target.checked property as the value instead of event.target.value:

      form-tutorial/src/components/App/App.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      ...
      
      function App() {
        const [formData, setFormData] = useReducer(formReducer, {});
        const [submitting, setSubmitting] = useState(false);
      
        const handleSubmit = event => {
          event.preventDefault();
          setSubmitting(true);
      
          setTimeout(() => {
            setSubmitting(false);
          }, 3000);
        }
      
        const handleChange = event => {
         const isCheckbox = event.target.type === 'checkbox';
         setFormData({
           name: event.target.name,
           value: isCheckbox ? event.target.checked : event.target.value,
         })
       }
      ...
      

      In this code, you use the ? ternary operator to make the conditional statement.

      Save the file. After the browser refreshes, fill out the form and click submit. You’ll find that the alert matches the data in the form:

      Form elements submitting correct data

      In this step, you learned how to create uncontrolled form components. You saved the form data to a state using the useReducer Hook and reused that data in different components. You also added different types of form components and adjusted your function to save the correct data depending on the element type.

      In the next step, you’ll convert the components to controlled components by dynamically setting the component value.

      Step 3 — Updating Form Data Using Controlled Components

      In this step, you’ll dynamically set and update data using controlled components. You’ll add a value prop to each component to set or update the form data. You’ll also reset the form data on submit.

      By the end of this step, you’ll be able to dynamically control form data using React state and props.

      With uncontrolled components, you don’t have to worry about synchronizing data. Your application will always hold on to the most recent changes. But there are many situations where you’ll need to both read from and write to an input component. To do this, you’ll need the component’s value to be dynamic.

      In the previous step, you submitted a form. But after the form submission was successful, the form still contained the old stale data. To erase the data from each input, you’ll need to change the components from uncontrolled components to controlled components.

      A controlled component is similar to an uncontrolled component, but React updates the value prop. The downside is that if you are not careful and do not properly update the value prop the component will appear broken and won’t seem to update.

      In this form, you are already storing the data, so to convert the components, you’ll update the value prop with data from the formData state:

      form-tutorial/src/components/App/App.js

      ...
        return(
          <div className="wrapper">
            <h1>How About Them Apples</h1>
            {submitting &&
              <div>
                You are submitting the following:
                <ul>
                  {Object.entries(formData).map(([name, value]) => (
                    <li key={name}><strong>{name}</strong>: {value.toString()}</li>
                  ))}
                </ul>
              </div>
            }
            <form onSubmit={handleSubmit}>
              <fieldset>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={handleChange} value={formData.name}/>
                </label>
              </fieldset>
              <fieldset>
                <label>
                  <p>Apples</p>
                  <select name="apple" onChange={handleChange} value={formData.apple}>
                      <option value="">--Please choose an option--</option>
                      <option value="fuji">Fuji</option>
                      <option value="jonathan">Jonathan</option>
                      <option value="honey-crisp">Honey Crisp</option>
                  </select>
                </label>
                <label>
                  <p>Count</p>
                  <input type="number" name="count" onChange={handleChange} step="1" value={formData.count}/>
                </label>
                <label>
                  <p>Gift Wrap</p>
                  <input type="checkbox" name="gift-wrap" onChange={handleChange} checked={formData['gift-wrap']}/>
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      As before, the checkbox is a little different. Instead of setting a value, you’ll need to set the checked attribute. If the attribute is truthy, the browser will show the box as checked.

      If you want to pre-fill the form, add some default data to the formData state. Set a default value for the count by giving formState a default value of { count: 100 }:

      form-tutorial/src/components/App/App.js

      ...
      
      function App() {
        const [formData, setFormData] = useReducer(formReducer, {
         count: 100,
       });
        const [submitting, setSubmitting] = useState(false);
      ...
      

      Save the file. When you do, the browser will reload and you’ll see the input with the default data:

      Form with default count

      Note: The value attribute is different from the placeholder attribute, which is native on browsers. The placeholder attribute shows information but will disappear as soon as the user makes a change; it is not stored on the component. You can actively edit the value, but a placeholder is just a guide for users.

      Now that you have active components, you can clear the data on submit. To do so, add a new condition in your formReducer. If event.reset is truthy, return an object with empty values for each form element. Be sure to add a value for each input. If you return an empty object or an incomplete object, the components will not update since the value is undefined.

      After you add the new event condition in the formReducer, update your submit function to reset the state when the function resolves:

      form-tutorial/src/components/App/App.js

      import React, { useReducer, useState } from 'react';
      import './App.css';
      
      const formReducer = (state, event) => {
        if(event.reset) {
         return {
           apple: '',
           count: 0,
           name: '',
           'gift-wrap': false,
         }
       }
        return {
          ...state,
          [event.name]: event.value
        }
      }
      
      function App() {
        const [formData, setFormData] = useReducer(formReducer, {
          count: 100
        });
        const [submitting, setSubmitting] = useState(false);
      
        const handleSubmit = event => {
          event.preventDefault();
          setSubmitting(true);
      
          setTimeout(() => {
            setSubmitting(false);
            setFormData({
             reset: true
           })
          }, 3000);
        }
      
      ...
      

      Save the file. When you do, the browser will reload and the form will clear on submit.

      Save the form and then clear the data

      In this step, you converted your uncontrolled components to controlled components by setting the value or the checked attributes dynamically. You also learned how to refill data by setting a default state and how to clear the data by updating the form reducer to return default values.

      In this next step, you’ll set form component properties dynamically and disable a form while it is submitting.

      Step 4 — Dynamically Updating Form Properties

      In this step, you’ll dynamically update form element properties. You’ll set properties based on previous choices and disable your form during submit to prevent accidental multiple submissions.

      Currently, each component is static. They do not change as the form changes. In most applications, forms are dynamic. Fields will change based on the previous data. They’ll validate and show errors. They may disappear or expand as you fill in other components.

      Like most React components, you can dynamically set properties and attributes on components and they will re-render as the data changes.

      Try setting an input to be disabled until a condition is met by another input. Update the gift wrapping checkbox to be disabled unless the user selects the fuji option.

      Inside App.js, add the disabled attribute to the checkbox. Make the property truthy if the formData.apple is fuji:

      form-tutorial/src/components/App/App.js

      ...
              <fieldset>
                <label>
                  <p>Apples</p>
                  <select name="apple" onChange={handleChange} value={formData.apple}>
                      <option value="">--Please choose an option--</option>
                      <option value="fuji">Fuji</option>
                      <option value="jonathan">Jonathan</option>
                      <option value="honey-crisp">Honey Crisp</option>
                  </select>
                </label>
                <label>
                  <p>Count</p>
                  <input type="number" name="count" onChange={handleChange} step="1" value={formData.count}/>
                </label>
                <label>
                  <p>Gift Wrap</p>
                  <input
                   checked={formData['gift-wrap']}
                   disabled={formData.apple !== 'fuji'}
                   name="gift-wrap"
                   onChange={handleChange}
                   type="checkbox"
                  />
                </label>
              </fieldset>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, the browser will reload and the checkbox will be disabled by default:

      Gift wrap is disabled

      If you select the apple type of Fuji, the element will be enabled:

      Gift wrap is enabled

      In addition to changing properties on individual components, you can modify entire groups of components by updating the fieldset component.

      As an example, you can disable the form while the form is actively submitting. This will prevent double submissions and prevent the user from changing fields before the handleSubmit function fully resolves.

      Add disabled={submitting} to each <fieldset> element and the <button> element:

      form-tutorial/src/components/App/App.js

      ...
            <form onSubmit={handleSubmit}>
              <fieldset disabled={submitting}>
                <label>
                  <p>Name</p>
                  <input name="name" onChange={handleChange} value={formData.name}/>
                </label>
              </fieldset>
              <fieldset disabled={submitting}>
                <label>
                  <p>Apples</p>
                  <select name="apple" onChange={handleChange} value={formData.apple}>
                      <option value="">--Please choose an option--</option>
                      <option value="fuji">Fuji</option>
                      <option value="jonathan">Jonathan</option>
                      <option value="honey-crisp">Honey Crisp</option>
                  </select>
                </label>
                <label>
                  <p>Count</p>
                  <input type="number" name="count" onChange={handleChange} step="1" value={formData.count}/>
                </label>
                <label>
                  <p>Gift Wrap</p>
                  <input
                    checked={formData['gift-wrap']}
                    disabled={formData.apple !== 'fuji'}
                    name="gift-wrap"
                    onChange={handleChange}
                    type="checkbox"
                  />
                </label>
              </fieldset>
              <button type="submit" disabled={submitting}>Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file, and the browser will refresh. When you submit the form, the fields will be disabled until the submitting function resolves:

      Disable form elements when submitting

      You can update any attribute on an input component. This is helpful if you need to change the maxvalue for a number input or if you need to add a dynamic pattern attribute for validation.

      In this step, you dynamically set attributes on form components. You added a property to dynamically enable or disable a component based on the input from another component and you disabled entire sections using the <fieldset> component.

      Conclusion

      Forms are key to rich web applications. In React, you have different options for connecting and controlling forms and elements. Like other components, you can dynamically update properties including the value input elements. Uncontrolled components are best for simplicity, but might not fit situations when a component needs to be cleared or pre-populated with data. Controlled components give you more opportunities to update the data, but can add another level of abstraction that may cause unintentional bugs or re-renders.

      Regardless of your approach, React gives you the ability to dynamically update and adapt your forms to the needs of your application and your users.

      If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link