newforms Documentation

repository·react·Indexed 20 days ago

https://github.com/insin/newforms

An isomorphic form-handling library for React, version 0.13.2, inspired by the Django forms framework. It enables developers to define form structures, manage user input, and handle validation across both server and browser environments. The library provides a base Form class for definition, various Field types (e.g., CharField, EmailField), and a BoundField class to facilitate custom form rendering and metadata access.

Tokens
44.5K
Snippets
134
Records
187
Agent score
70%

What's inside newforms

  1. What is a BoundField and how to use it for custom rendering

    react

    A BoundField is a helper object used to render HTML content for a single field. It acts as a bridge between the Field definition, its configured Widget, the field's name in the Form, and the current state of user input and validation errors.

    Instead of using the default react_components, you can use BoundField to manually construct your UI. This allows you to control exactly how labels, inputs, help text, and error messages are displayed.

    Forms provide three ways to access BoundField instances:

    • form.boundFields(): Returns a list of BoundField objects in the order they were defined in the form.
    • form.boundFieldsObj(): Returns an object where keys are field names and values are their corresponding BoundField objects.
    • form.boundField(fieldName): Returns the BoundField for a specific named field.
    // Accessing BoundFields from a form instance
    const fieldsList = form.boundFields();
    const fieldsMap = form.boundFieldsObj();
    const singleField = form.boundField('email');
  2. Manage Form state with onChange()

    react

    While a Form is not a React component, it is stateful. Its data, errors(), and cleanedData properties change as users provide input. To ensure your React component re-renders when the form state changes, you must provide an onChange() callback when instantiating the Form.

    Warning: If you are using Controlled user inputs, failing to pass an onChange() callback will result in your form inputs being read-only. The development version of newforms will issue a warning in this case.

    Typically, the onChange callback calls this.forceUpdate() to sync the React component with the Form's internal state.

    getInitialState: function() {
      return {
        // Pass the callback to the Form constructor
        form: new ContactForm({onChange: this.onFormChange})
      }
    },
    
    onFormChange: function() {
      // Force React to re-render with the updated form state
      this.forceUpdate()
    }
  3. How MultiWidget and MultiValueField work together

    react

    A MultiWidget is a widget composed of multiple individual widgets. It is typically used in conjunction with a MultiValueField.

    Implementation Requirements

    When extending MultiWidget, you must implement the decompress method. This method takes a single "compressed" value from the form field and returns a list of "decompressed" values to be distributed among the sub-widgets.

    Key Methods

    • render(name, value, kwargs): Renders the collection of widgets. value may be a single value that needs splitting or a list of values.
    • decompress(value): Splits a single value into a list of values for the constituent widgets. This must be implemented defensively to handle empty values.
    • formatOutput(renderedWidgets): A hook to customize the HTML wrapper. By default, widgets are wrapped in a <div>.
    class SplitDateTimeWidget extends MultiWidget {
      decompress(value) {
        // Logic to split a Date into [datePart, timePart]
        return [date, time];
      }
    }
  4. Understand pre-configured locales

    react

    Newforms includes two built-in locales:

    • en: The default locale. It expects forward slash delimited date inputs in month/day/year format and defaults to year-month-day for display in inputs.
    • en_GB: A quick way to switch to day/month/year date input requirements.
  5. How FormSets work

    react

    A FormSet is an abstraction used to manage multiple instances of the same form on a single page, similar to a data grid. You create a formset by extending forms.FormSet and providing a base form class.

    To render the forms, you iterate over the collection returned by formset.forms().

    Key configuration options:

    • form: The base Form class to be used for all instances in the set.
    • extra: The number of additional blank forms to display beyond the number of forms generated from initial data. Defaults to 1.
    • maxNum: Limits the maximum total number of forms (initial + extra) the formset will display.
    var ArticleForm = forms.Form.extend({
      title: forms.CharField(),
      pubDate: forms.DateField()
    });
    
    // Create the FormSet
    var ArticleFormSet = forms.FormSet.extend({
      form: ArticleForm,
      extra: 2,
      maxNum: 5
    });
    
    // Instantiate and iterate
    var formset = new ArticleFormSet();
    formset.forms().forEach(function(form) {
      // Render each form instance
      print(reactHTML(<RenderForm form={form}/>))
    });
  6. Use controlled React components for user inputs

    react

    By default, newforms generates uncontrolled React components. These can provide initial values but require manual DOM updates if you want to change displayed values programmatically.

    To programmatically update values, use controlled components by passing the controlled: true argument when constructing a Form or individual Fields.

    Controlled components reflect the values held in form.data. To update these values, use form.setData() or form.updateData(). These methods handle the transition from initial data to user input and automatically call onChange() to trigger React re-renders.

    var form = new SignupForm({
      controlled: true, 
      onChange: this.onFormChange
    });
  7. How the Widget base class works

    react

    The Widget class is the base for all HTML form widgets in newforms. It handles HTML rendering and data extraction. Note that Widget itself is an abstract base class and cannot be rendered directly; you must implement the render method when extending it.

    Key Properties

    • widget.attrs: An object containing base HTML attributes for the rendered widget.
    • widget.isHidden: Boolean indicating if the widget renders as <input type="hidden">.
    • widget.needsMultipartForm: Boolean indicating if the widget requires a multipart-encoded form (e.g., for file uploads).
    • widget.needsInitialValue: Boolean indicating if the render logic should always use the initial value.
    • widget.isRequired: Boolean indicating if the widget represents a required field.

    Key Methods

    • render(name, value, kwargs): Returns a ReactElement. Must be implemented by subclasses.
      • name: The name for the widget or the basis for unique names if multiple inputs are needed.
      • value: The value to display.
      • kwargs.attrs: Additional HTML attributes.
      • kwargs.controlled: If true, renders a controlled component.
      • kwargs.initialValue: Passed if widget.needsInitialValue is true.
    • valueFromData(data, files, name): Retrieves the widget's value from form data.
    • idForLabel(id): Returns the HTML id attribute used for <label> association. For widgets with multiple elements, return the ID of the first element.
    • subWidgets(name, value, kwargs): A generator that yields "subwidgets" (used by classes like RadioSelect).
    class MyCustomWidget extends Widget {
      render(name, value, kwargs) {
        // Implementation returning a ReactElement
      }
    }
  8. Understand the ManagementForm

    react

    The ManagementForm is a hidden form used to track the state of a FormSet. It is essential when submitting formsets to a server-side process to ensure the number of forms is correctly interpreted.

    Required fields in the submitted data:

    • form-TOTAL_FORMS: Total number of forms in the set.
    • form-INITIAL_FORMS: Number of forms pre-filled from initial data.
    • form-MAX_NUM_FORMS: The maximum number of forms allowed.

    Warning: If you are using newforms on the server to handle formsets and do not provide this management data, an error will be thrown: ManagementForm data is missing or has been tampered with.

    Client-side usage:

    • If adding forms via JavaScript, you must manually increment the count fields in the ManagementForm.
    • If deleting forms via JavaScript, ensure the removed forms are marked for deletion by including form-#-DELETE in the POST data.
    • To keep these fields in sync automatically when using client-side FormSets, you can render formset.managementForm().
  9. Define a Form using Form.extend()

    react

    Forms are defined by extending the base forms.Form class using Form.extend(). You define fields (like CharField, EmailField, BooleanField) and can optionally specify a widget to control how the input is rendered (e.g., forms.PasswordInput).

    Key concepts:

    • Field: Represents a piece of user input data.
    • Form: A group of related Fields.
    • Widget: The form input displayed to the user (every Field has a default Widget).
    var SignupForm = forms.Form.extend({
      username: forms.CharField(),
      email: forms.EmailField(),
      password: forms.CharField({widget: forms.PasswordInput}),
      confirmPassword: forms.CharField({widget: forms.PasswordInput}),
      acceptTerms: forms.BooleanField({required: true})
    })
  10. Use newforms in isomorphic (server-side) applications

    react

    Newforms is DOM-independent, making it suitable for isomorphic (universal) JavaScript applications.

    • Server-side: You can use newforms to pre-render initial HTML on the server and validate incoming POST data.
    • Client-side: The rendered HTML can be rehydrated by React on the client.
    • Compatibility: Because newforms generates standard HTML name attributes, the input data is fully compatible with standard HTTP POST submissions, allowing for easy fallback to traditional server-side form handling.
  11. Perform form-level cleaning and cross-field validation

    react

    Form-level validation (often called clean()) is used for logic that depends on multiple fields. There are two ways to report errors during this step:

    1. Throw a ValidationError: This typically displays the error at the top of the form.
    2. Use addError(fieldName, message): This assigns the error to specific fields. Note that addError() automatically removes the field from cleanedData.

    To optimize performance during partial updates (e.g., onChange events), you can specify which fields trigger a specific clean() method by passing an array where the first elements are field names followed by the cleaning function.

    // Option 1: Throwing a global error
    var ContactForm = forms.Form.extend({
      clean: function() {
        if (someCondition) {
          throw forms.ValidationError("Global error message");
        }
      }
    });
    
    // Option 2: Assigning errors to specific fields
    var ContactForm = forms.Form.extend({
      clean: function() {
        var cleanedData = ContactForm.__super__.clean.call(this);
        if (condition) {
          var message = "Field specific error";
          this.addError('ccMyself', message);
          this.addError('subject', message);
        }
      }
    });
    
    // Option 3: Specifying fields for cross-field validation
    var PersonForm = forms.Form.extend({
      firstName: forms.CharField({required: false}),
      lastName: forms.CharField({required: false}),
      // Only runs if firstName or lastName changes
      clean: ['firstName', 'lastName', function() {
        if (!this.cleanedData.firstName && !this.cleanedData.lastName) {
          throw forms.ValidationError('A first name or last name is required.');
        }
      }]
    });
  12. Understand the validation lifecycle and order

    react

    Validation in newforms occurs during the data cleaning process (e.g., when calling form.validate()). The process follows a specific hierarchy from individual field coercion to global form validation.

    The execution order for each field is:

    1. Field.toJavaScript(): Coerces raw widget input into the correct JavaScript datatype (e.g., converting a string to a Number in a FloatField). Throws ValidationError if coercion fails.
    2. Field.validate(): Handles field-specific logic that isn't a reusable validator. It operates on the coerced value.
    3. Field.runValidators(): Executes all registered validator functions and aggregates their errors into a single ValidationError.
    4. Field.clean(): The orchestrator for the steps above. It returns the cleaned data to be placed in the form's cleanedData object.
    5. Form.clean<FieldName>() (or clean_<fieldName>()): A field-specific hook on the Form level. It is called after the field itself has been cleaned. You access the field's value via this.cleanedData.
    6. Form.clean(): The final step. This is used for cross-field validation (e.g., checking if password matches password_confirmation).

    Error Propagation Rules:

    • If field.clean() throws a ValidationError, the field-specific clean<FieldName>() hook for that field is not called.
    • However, the cleaning process continues for all remaining fields in the form.