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 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-redux-form-base

v3.0.0

Published

Provides a composable form structure with validation

Readme

react-redux-form-base

There a lot of libraries out there managing the form. However, all them force specific Input components or form items. This library only defines the workflow to capture data and validate it.

It is really recommended using the state recipe, as forms usually are transient data and this type of data does not need to be kept in memory.

2 ways to manage the form data. Either through the component state or redux state. So, see following cookbook recipes.

For both cases, please, assume that we have the following Input component and schemaData...

Input

class Input extends PureComponent {
  static propTypes = {
    type: PropTypes.string,
    value: PropTypes.any
  };

  static defaultProps = {
    type: "text",
    value: ""
  };

  render() {
    return <input {...this.props} />;
  }
}

schemaData

import React from "react";
import { combineValidations } from "react-redux-form-base";
// or import { combineValidations } from "react-redux-form-base/lib/formUtils";

export default {
  "contacts[].value": combineValidations(({ data, path, value }) => {
    const isValid = !!value;
    const message = isValid ? null : (
      <span className="react__form-item-validation-message">
        Contact is required
      </span>
    );
    return { data, isValid, message, path };
  }),
  "attributes[]": combineValidations(({ data, path, value }) => {
    const isValid = !!value;
    const message = isValid ? null : (
      <span className="react__form-item-validation-message">
        Attribute is required
      </span>
    );
    return { data, isValid, message, path };
  }),
  age: combineValidations(({ data, path, value }) => {
    const isValid = !!value && /\d+$/g.test(value);
    const message = isValid ? null : (
      <span className="react__form-item-validation-message">
        Age is required and only accepts numbers
      </span>
    );
    return { data, isValid, message, path };
  }),
  title: combineValidations(
    ({ data, path, value }) => {
      const isValid = !!value && /^(([m][r])([s]?))/gi.test(value);
      const message = isValid ? null : (
        <span className="react__form-item-validation-message">
          The only allowed ones are {'"Mr"'} or {'"Mrs"'}.
        </span>
      );
      return { data, isValid, message, path };
    },
    ({ data, path, value }) => {
      const isValid = !!value;
      const message = isValid ? null : (
        <span className="react__form-item-validation-message">
          Title is required!
        </span>
      );
      return { data, isValid, message, path };
    }
  ),
  firstname: combineValidations(({ data, path, value }) => {
    const isValid = !!value;
    const message = isValid ? null : (
      <div className="react__form-item-validation-message">
        Firstname is required
      </div>
    );
    return { data, isValid, message, path };
  }),
  lastname: combineValidations(({ data, path, value }) => {
    const isValid = !!value;
    const message = isValid ? null : (
      <div className="react__form-item-validation-message">
        Lastname is required
      </div>
    );
    return { data, isValid, message, path };
  }),
  "certificate.description": combineValidations(
    ({ data, path, value }) => {
      const isValid = !!value && /^(((\w+)\s+){9}(\w+))/g.test(value);
      const message = isValid ? null : (
        <div className="react__form-item-validation-message">
          It requires 10 words as description at least.
        </div>
      );
      return { data, isValid, message, path };
    },
    ({ data, path, value }) => {
      const isValid = !!value;
      const message = isValid ? null : (
        <div className="react__form-item-validation-message">
          Certificate description is required!
        </div>
      );
      return { data, isValid, message, path };
    }
  )
};

Component State

Here the state is kept in the component state which uses the new context api to provide the events to control data and validation messages. In this recipe, the developer controls when the validation should be displayed. The most important thing is that this recipe defines workflow for capturing data and validating it in the most flexible way.

It worth mention that in the children as function there are 3 nice functions provided:

  • onChange, which handle an event updating the path with e.target.value / (e) => { ...internalUpdate(e.target.value)... )};
  • onChangeValue, which updates the path with given value (value) => { ...internalUpdate(value)... )};
  • isAllValid, which validates a restricted groups of paths required, or all them when restrictions are not provided;
    • isAllValid() validates all the validations associated with the shema and FormStateItem defined for path;
    • isAllValid(['firstname', 'lastname']) verifies only the validations associated with firstname and lastname;
import React, { Component } from "react";
import { FormState, FormStateItem } from "react-redux-form-base";

class AppStateForm extends Component {
  state = {
    person: {},
    shouldValidate: false
  };

  onSubmit = e => {
    e.preventDefault();
    alert("Sending form data...");
  };

  onValidate = e => {
    e.preventDefault();
    this.setState({ shouldValidate: true });
  };

  render() {
    const { shouldValidate } = this.state;
    return (
      <div>
        <h2>FormState</h2>
        <FormState schemaData={schemaData}>
          <div>
            <FormStateItem path="firstname">
              {({ onChange, value, validations }) => (
                <span data-form-state-item>
                  <Input id="firstname" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </FormStateItem>
            <br />

            <label htmlFor="lastname">Lastname</label>
            <FormStateItem path="lastname">
              {({ onChange, value, validations }) => (
                <span data-form-state-item>
                  <Input id="lastname" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </FormStateItem>
            <br />

            <FormStateItem>
              {({ isAllValid }) => (
                <button
                  onClick={this.onValidate}
                  disabled={!isAllValid(["fistname", "lastname"])}
                >
                  Validate Certificate
                </button>
              )}
            </FormStateItem>
          </div>

          <label htmlFor="description">Certificate description</label>
          <FormStateItem path="certificate.description">
            {({ onChange, value, validations }) => (
              <span>
                <Input id="description" onChange={onChange} value={value} />
                {shouldValidate &&
                  validations &&
                  validations.map(({ message }) => message)}
              </span>
            )}
          </FormStateItem>
          <br />

          <FormStateItem>
            {({ isAllValid }) => (
              <button onClick={this.onSubmit} disabled={!isAllValid()}>
                Submit
              </button>
            )}
          </FormStateItem>
        </FormState>
      </div>
    );
  }
}

Redux recipe

import React, { Component } from "react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import logger from "redux-logger";
import createComponent from "react-redux-form-base";

/**
 * The createComponent function requires a namespace which names the reducer
 * and also prefix all the actions. The, the property name where the form data
 * will be stored. And finally, the schemaData that validates the form.
 *
 * In this case, we want to keep the create a reducer "personSection" within
 * a property "person" and "schemaData" previously mentioned. So, as result,
 * the function returns a connected Model (Component) and the reducers containing
 * the named reducer.
 **/
const { Model, reducers, connectActions } = createComponent(
  "personSection",
  "person",
  schemaData
);
const ONE_MINUTE = 60 * 1000;

class AppModel extends Component {
  componentWillMount() {
    this.store = createStore(
      combineReducers(reducers),
      applyMiddleware(logger)
    );
    this.actions = connectActions(this.store);
    /**
     * Here we are resetting the form every 1 minute. Don't take it as a real use case.
     * You might get the action connecting your component. Also, it is recommended to use
     * the state in the form. We should not be using the store state to keep transient
     * data.
     * */
    this.intervalRef = setInterval(() => this.actions.setData({}), ONE_MINUTE);
  }

  componentWillUnmount() {
    clearInterval(this.intervalRef);
  }

  render() {
    return (
      <div>
        <Provider store={this.store}>
          <form>
            <h2>ReduxForm</h2>
            <label htmlFor="title">Title</label>
            <Model path="title">
              {({ onChange, value, validations }) => (
                <span>
                  <Input id="title" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            <label htmlFor="firstname">Firstname</label>
            <Model path="firstname">
              {({ onChange, value, validations }) => (
                <span>
                  {/**
                   * onChange is a connected actions that changes the field
                   * value based on the path. And, the value is a result extracted
                   * using the path.
                   **/}
                  <Input id="firstname" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            <label htmlFor="lastname">Lastname</label>
            <Model path="lastname">
              {({ onChange, value, validations }) => (
                <span>
                  <Input id="lastname" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            {/* Note the special case for complex and primitive lists. */}
            <label htmlFor="contacts">Contacts</label>
            <Model path="contacts[0].value">
              {({ onChange, value, validations }) => (
                <span>
                  <Input
                    id="contacts[0].value"
                    onChange={onChange}
                    value={value}
                  />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            <Model path="contacts[1].value">
              {({ onChange, value, validations }) => (
                <span>
                  <Input
                    id="contacts[1].value"
                    onChange={onChange}
                    value={value}
                  />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            <label htmlFor="attributes">Attributes</label>
            <Model path="attributes[0]">
              {({ onChange, value, validations }) => (
                <span>
                  <Input id="attributes[0]" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            <Model path="attributes[1]">
              {({ onChange, value, validations }) => (
                <span>
                  <Input id="attributes[1]" onChange={onChange} value={value} />
                  {validations && validations.map(({ message }) => message)}
                </span>
              )}
            </Model>
            {/**
             * When no path is provided, it still give access to the
             * full form data and connected actions such as onChange,
             * setShouldValidate and setData.
             **/}
            <Model>
              {({ setShouldValidate }) => (
                <button
                  id="btnValidate"
                  type="button"
                  onClick={() => setShouldValidate(true)}
                >
                  Validate
                </button>
              )}
            </Model>
          </form>
        </Provider>
      </div>
    );
  }
}

API

Test

npm test