npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@acmedinotech/dynform-react

v0.0.7

Published

managed stateful, nested form data with ease and efficiency

Downloads

15

Readme

Dynamic Form for React (dynform-react)

dynform-react (aka dfr) attempts to accomplish the following:

  • effecient rendering of forms by only updating the parent state when controls lose focus
  • convenient hooks and contexts that allow arbitrary control of the parent form
  • transparent management of embedded forms

First, a Quick Demo

import { DynamicForm, DynamicFormContext, ManagedControl } from '@acmedinotech/dynform-react';

const MyForm = (props:any) => {
  const SaveButton = () => {
    const formCtx = useContext(DynamicFormContext);
    return <button onClick={() => {
      formContext.saveFn();
    }}>save</button>
  }

  console.log({formStatus: formCtx.formStatus})

  return <DynamicForm
    formId="test"
    saveFn={async (data: any) => {
      console.log({saveFn: data});
      return {status: 'ok' /* or 'err' */, errors: {}}
    }}
    ping={(action, curState, diff) => {
      console.log({action, curState, diff});
    }}
    >
    First Name: <ManagedControl><input type="text" name="firstName" placeholder="First Name" /></ManagedControl> <br />
    <SaveButton />
  </DynamicForm>
}

When this form renders, changing firstName won't trigger any events. If you click away from it, however, you should expect to see:

{ formStatus: { status: '' }}
{ action: "beforeStateChange", curState: {}, diff: { firstName: "Your Value" }}
{ formStatus: { status: 'dirty' }}

When you click save, you should expect to see:

{ saveFn: { firstName: "Your Value" } }
{ formStatus: { status: 'ok' }}

With very little effort, you immediately have access to dynamic form capabilities that follow React state conventions.

Efficient Rendering

Using basic React state management approaches, each control will update the entire state whenever onChange is triggered. For complex forms, this will result in a slower UI (and require you to save your control focus/cursor position). The ideal approach is to update the control on-change, and when a user is done with it, update the parent state.

dfr solves this in two ways:

  1. <DynamicForm/> sets up a DynamicFormContext for its component tree
  2. individual controls wrapped with <ManagedControl/> have their onChange and onBlur hijaacked:
    • onChange updates control-specific state
    • onBlur takes the current control state and triggers form update via context

Convenient Hooks and Contexts

The DynamicFormContext provides the ability to change form state for whatever form a component is embedded in, and allows observation of upcoming changes. This saves you a considerable amount of overhead in wiring up this flow yourself.


A set of components and helpers to handling form interactions.

The <DynamicForm /> accepts a set of controls, which can include other forms:

<DynamicForm fields={[
    {
        name: 'firstName',
        control: <input type='text' />
    },
    {
        name: 'address',
        control: <DynamicForm fields={[
            {
                name: 'line1',
                control: <input type='text' />
            }
        ]}>
    }
]}>

A control's onChange handler is hijacked -- to preprocess a value before the form deals with it, set getControlValue(event) => any, otherwise the control's value or checked state will be used.

If you're supplying an embedded form, a different mechanism is used to communicate changes, which is described in the context section.

Lifecycle Extensions

Forms accept an async saveFn(state) which is given the form's current state and is expected to return a FormStatus object reflecting whether or not save succeeded.

Forms also accept a ping(action,curState,change) which is called before a form state change.

Status

Indicates the general health of the form with status as err (error), dirty (unsaved), or ok (default). Additional keys are supported:

  • errors: for status:'err', this object is expected to have a string message or _errMap with list of field names/messages
  • payload: for status:'ok', this object should hold the most current version of the state (i.e. after saving to a server, set this as the saved value)

Status changes are expected to occur when a control changes, when the form is validated, and when a save completes.

Value Resolution

In order to capture the most current control value, the effective value is taken to be state[controlId] ?? formStatus?.payload?.[controlId] ?? initialValue?.[controlId], meaning:

  • the most current state value
  • the most current value from formStatus.status == 'ok'
  • the initial value provided on component init

General Form Context Manager

The general form context manager (formCtx for short) exposes form data and various state operations that embedded controls can leverage for advanced changes. When state changes occur here, they change the state of the providing DynamicForm. <DynamicForm />s can be embedded, and a dependency is built between the child context and parent so that whenever a child's status changes, the parent is notified. We'll come back to this again.

setState and ping

Partial changes can be persisted through setState:

const formCtx = useContext(DynamicFormContextManager);
// ...
formCtx.setState({oldField: 'newValue'});

The DynamicForm can receive a ping function that, if defined, is called within setState before the change is persisted:

<DynamicForm ping={(action, curState, change) => {
    console.log('state is gonna change!', action, {curState, change});
}} />

This allows the caller to take additional steps in response, such as a nested control refreshing its own state before the parent form re-renders.

setFormStatus

Marks the form status as err (error), dirty (unsaved), or ok (default). Additional keys are supported:

  • errors: for status:'err', this object is expected to have a string message or _errMap with list of field names/messages
  • payload: for status:'ok', this object should hold the most current version of the state (i.e. after saving to a server, set this as the saved value)

saveFn

Triggers the save handler of the caller. It's expected to return a formStatus object, which will trigger setFormStatus.

Form Data Flow

  • ℹ️ state = initialState ?? {}
  • ℹ️ formStatus = {}
  • onChange: 🔔 ping, 🔄 setState
  • onBlur: if changes have occurred, 🔔 saveFn, 🔄 setFormStatus
    • ℹ️ formStatus = {status: 'ok' or 'err'}