Giter Site home page Giter Site logo

teslamotors / informed Goto Github PK

View Code? Open in Web Editor NEW
940.0 14.0 172.0 88.5 MB

A lightweight framework and utility for building powerful forms in React applications

Home Page: https://teslamotors.github.io/informed

License: MIT License

JavaScript 96.20% CSS 2.72% TypeScript 0.76% HTML 0.32%

informed's Introduction

Informed

Docs npmversion github Docs

Introduction

Say hello to the best React form library you have ever used! Informed is an extensive, simple, and efficient solution for creating basic to complex forms in React. Out of the box you get the ability to grab and manipulate values, validate fields, create custom inputs, multi-step forms, array fields, and much much more!

Oh and YES WE USE HOOKS!

Getting Started

Install with npm
npm install --save informed

Live Examples / Docs

Docs Docs

What Can it do ?

See for yourself.

By default it comes with native dom inputs that are controlled by informed.

import { Form, Input, Select, Checkbox, Relevant, Debug } from 'informed';

const onSubmit = ({ values }) => console.log(values);

const ExampleForm = () => (
  <Form onSubmit={onSubmit}>
    <Input name="name" label="Name" placeholder="Elon" />
    <Input name="age" type="number" label="Age" required="Age Required" />
    <Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
    <Select name="car" label="Car" initialValue="ms">
      <option value="ms">Model S</option>
      <option value="m3">Model 3</option>
      <option value="mx">Model X</option>
      <option value="my">Model Y</option>
    </Select>
    <Checkbox name="married" label="Married?" />
    <Relevant when={({ formState }) => formState.values.married}>
      <Input name="spouse" label="Spouse" />
    </Relevant>
    <button type="submit">Submit</button>
    <Debug />
  </Form>
);

Feature List

informed was designed to support many important features

  • Arrays: ability to render dynamic arrays of fields [ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]
  • Relevance: ability to render render fields conditionally depending on the state of other parts of the form
  • JSPAN: ability to easily and intuitively manipulate form state
  • Formatting: ability to perform display formatting, where the format shown to user can differ from the state of the values stored
  • Validation: ability to perform both synchronous and asynchronous validation in a controlled manner
  • Api: ability to manipulate the form state both inside and outside the context of the form
  • State: ability to access field and form data
  • Multistep: ability to create dynamic multistep forms
  • Scope: ability to scope ( group ) fields
  • Schema: ability to render forms based on pure JSON schema
  • Dynaic: ability to hide and show fields ( render and unrender ) and either cleanup or maintain state of unmounted fields
  • Debugging: ability to easily debug users state as well as internals of the library
  • Nesting: ability to have highly nested value strucutre state.values.friends[1].brother.parents.cars[0].model

Creating Your Own Fields

But what if you dont want the out of the box stuff??

No problem, see example below!

import { useForm, useField, Relevant, FormState } from 'informed';

// Step 1. Build your form component ---------------------

const Form = ({ children, ...rest }) => {
  const { formController, render, userProps } = useForm(rest);

  return render(
    <form noValidate {...userProps} onSubmit={formController.submitForm}>
      {children}
    </form>
  );
};

// Step 2. Build your input components --------------------

const Input = props => {
  const { render, informed, userProps, ref } = useField({
    type: 'text',
    ...props
  });
  const { label, id, ...rest } = userProps;
  return render(
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} ref={ref} {...informed} {...rest} />
    </>
  );
};

const Checkbox = props => {
  const { render, informed, userProps, ref } = useField({
    type: 'checkbox',
    ...props
  });
  const { label, id, ...rest } = userProps;
  return render(
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} ref={ref} {...informed} {...rest} />
    </>
  );
};

const ErrorInput = props => {
  const { render, informed, userProps, fieldState, ref } = useField({
    type: 'text',
    ...props
  });
  const { label, id, ...rest } = userProps;
  const { showError } = fieldState;
  const style = showError ? { border: 'solid 1px red' } : null;
  return render(
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} ref={ref} {...informed} {...rest} style={style} />
      {showError && <small style={{ color: 'red' }}>{fieldState.error}</small>}
    </>
  );
};

const Select = props => {
  const { render, informed, userProps, ref } = useField({
    type: 'select',
    ...props
  });
  const { label, id, children, ...rest } = userProps;
  return render(
    <>
      <label htmlFor={id}>{label}</label>
      <select id={id} ref={ref} {...informed} {...rest}>
        {children}
      </select>
    </>
  );
};

// Step 3. Build your forms! ---------------------------

const onSubmit = ({ values }) => console.log(values);

const ExampleForm = () => (
  <Form onSubmit={onSubmit}>
    <Input name="name" label="Name" placeholder="Elon" />
    <ErrorInput name="age" type="number" label="Age" required="Age Required" />
    <Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
    <Select name="car" label="Car" initialValue="ms">
      <option value="ms">Model S</option>
      <option value="m3">Model 3</option>
      <option value="mx">Model X</option>
      <option value="my">Model Y</option>
    </Select>
    <Checkbox name="married" label="Married?" />
    <Relevant when={({ formState }) => formState.values.married}>
      <Input name="spouse" label="Spouse" />
    </Relevant>
    <button type="submit">Submit</button>
    <Debug />
  </Form>
);

For Contributors

Design

Informed took the following into consideration when being built:

  • Performance: informed was designed to be able to handle very complex forms at scale
    • Ability to render a form with thousands of fields on screen
    • Ability to bulk update thousands of fields at a time
  • Readability informed was designed to be able to write complex forms with very litle intuitive code
    • Reads naturlly like with pure JSX:
<Form onSubmit={onSubmit}>
  <Input name="name" label="Name" placeholder="Elon" />
  <Input name="age" type="number" label="Age" required="Age Required" />
  <Input name="phone" label="Phone" formatter="+1 (###)-###-####" />
  <Checkbox name="married" label="Married?" />
  <Relevant when={({ formState }) => formState.values.married}>
    <Input name="spouse" label="Spouse" />
  </Relevant>
  <Debug />
</Form>
  • ZERO Dependency: informed was designed to rely on no other library

    • exception of a peer dependency react... for now ;)
  • JSON Schema: informed was designed to support rendering forms based on pure JSON

    • this is especially useful when form definitions are stored on the backend
  • Feature List: informed was designed to support many important features

    • Arrays: ability to render dynamic arrays of fields [ 'a', 'b' ] or [ { name: 'Joe', age: 29 }, { name: 'Hope', age: 24 }]
    • Relevance: ability to render render fields conditionally depending on the state of other parts of the form
    • JSPAN: ability to easily and intuitively manipulate form state
    • Formatting: ability to perform display formatting, where the format shown to user can differ from the state of the values stored
    • Validation: ability to perform both synchronous and asynchronous validation in a controlled manner
    • Api: ability to manipulate the form state both inside and outside the context of the form
    • State: ability to access field and form data
    • Multistep: ability to create dynamic multistep forms
    • Scope: ability to scope ( group ) fields
    • Schema: ability to render forms based on pure JSON schema
    • Dynaic: ability to hide and show fields ( render and unrender ) and either cleanup or maintain state of unmounted fields
    • Debugging: ability to easily debug users state as well as internals of the library
    • Nesting: ability to have highly nested value strucutre state.values.friends[1].brother.parents.cars[0].model

Terminology

  • JSPAN: ( Java Script Path Access Notation ) much like how you access and write to objects and arrays in javascript you can use the string representation to address a place in an object.
const path = 'state.values.friends[1].brother.name';

Layout

This project cotains three important directories, src, vitedocs, and __tests__

Note: some things were left out as they are not super important or are going to be deprecated or removed in future.

project_root
│
├── index.d.ts        # all of informeds types live here
│
├── src               # all of informeds source code lives here ( except types index.d.ts )
│   ├── components        # React Components
│   ├── hooks             # Hooks
│   ├── Context.js        # Internal Contexts used in this library
│   ├── debug.js          # Basically the https://github.com/visionmedia/debug library but shrunk down
│   ├── fieldMap.js       # Default field adapter, used when working with schema forms
│   ├── index.js          # all external exports ( everything exposed to users )
│   ├── ObjectMap.js      # internal data structure for manipulating the form state objects
│   ├── FormController.js # The brains behind the library, this controls basically everything :)
│   └── utils.js          # Any utilities used throughout this project
│
├── vitedocs          # All the informed docs build via vite ( instead of storybook which was old way )
│   ├── App.jsx           # basic react app with react-router
│   ├── Header            # top nav of the docs
│   ├── hooks             # helper hooks for docs
│   ├── Nav               # side nav of the docs
│   ├── Pages             # main level pages of the app
│   │   ├──ApiReference       # Self explanitory :)
│   │   ├──Examples           # Examples of all sorts of usecases
│   │   ├──GettingStarted     # Also Self explanitory :)
│   │   └──Playground.jsx     # Uses Sandpack to allow users to test any of the examples
│   │
│   ├── SideBySide.jsx    # helper component for showing code example and output side by side
│   ├── index.css         # documentation styles
│   ├── prism.css         # styles for code blocks
│   └── ...               # other stuff
│
└── __tests__           # extensive unit testing
    ├── components      # tests for informed components
    ├── hooks           # tests for informed hooks
    ├── ObjectMap.test  # tests for the internal data structure
    ├── Schema.test     # tests for usage of JSON schema rendered forms
    └── utils.test      # tests for interanal library utilites

Key Components

FormController

FormController is the brains of informed, it holds the state object and is responsible for:

  • Managing the form state
  • Tracking all form fields via registration/deregistration
  • Managing events. FormController impliments its own event hanlder ( a few lines of code at bottom of file )

ObjectMap

ObjectMap is the internal data structre that is responsible for managing the internal state object. Reads and writes all go through this data structure. Example:

// State object
const state = { values: {} };

// Set the states values first friends brothers age to 30
ObjectMap.set(state.values, 'friends[0].brothers.age', 30);

useField

useField is the first class citizen of informed, its responsible for registering a field by name to the FormController.

Context

Thouh there is not explicitly a component called Context here the concept is KEY to understanding informed. Context alows us to register fields in a highly nested structure and allows us to do wild things such as scoping.


Architecture Diagram

Below depicts the core to how this library works. Form controller is the master of all form elements and everyone else just subscribes to events that it triggers. The events can be caused by function calls from elsewhere. In the example below we depict what happens when a user types in an "H" in the name field

Code:
<Form>
  <Input name="name" /> {/* --> useField("name") --> useFieldState("name") */}
  <Debug /> {/* --> useFormState() */}
</Form>
Diagram: ( when user types the letter "H" )
     +----------------+
     | FormController | < ──────────────────────────────────────────
     |   state {}     |                                             │
     +-------+--------+                                             │
             │                                                      │
             │ event("field", "name")                               │
             v                                                      │
    +-------------------+                                           │
    |    Event System   | ────────────────                          │
    +--------+----+-----+                 │                         │
             │                            │                         │
             │ event("field", "name")     │ event("field", "name")  │ setValue("H")
             │                            │                         │
             v                            v                         │
    +-------------------+       +------------------------+          │
    |   useFormState()  |       |  useFieldState("name") |          │
    +-------------------+       +------------------------+          │
             ^                            ^                         │
             │ uses                       │ uses                    │
             │                            │                         │
    +-------------------+       +------------------------+          │
    |      <Debug />    |       |    useField("name")    | ─────────
    +-------------------+       +------------------------+
                                          ^
                                          │ uses
                                          │
                                +------------------------+
                                | <Input name="name" />  |
                                +------------------------+

Types ( Type Script )

I know, I know the types kinda suck. I personally dont use typescript so I have had to rely on other TS wizard devs to help maintain the types. When trying to type very complex objects such as FormState especially parts like FormState.values which can literally be an object that conatins anything there are lots of opinions on how to properly type it. Im activly seeking help here.


Documentation

As stated earlier, docs now live in the vitedocs directory. Its called vitedocs because I migrated them from storybook to now use a custom singe page react app built with vite... pernounced "veet" by the way :)

What to expect

Most of the docs contain examples where each example has a directory.

For example, if you look at the vitedocs/Pages/GettingStarted/CarColors you will find the CarColors.jsx file. This file contains the page that gets shown ( whatever info you want to show on that page ) and then the example itself.

Because we use vite, we can take advantage of the fact that vite can import both a react component normally, and also the raw text from that file! Therefore creating examples is as simple as this!

Note how we make use of the SideBySide component to render our code and example

import Example from './Example';
import exampleCode from './Example.jsx?raw';

//... other stuff
<SideBySide
  leftHeader={<h3>Example: </h3>}
  rightHeader={<h3>Code:</h3>}
  left={<Example />}
  right={<Code links input1={exampleCode} />}
/>;

TODO/Improvements

  • re-write the internal implimentation of how schemas are rendered, that code is not my favorite
  • re-write the way multistep forms work. Currently they step through each step when navigating to a step only initializing the values for that step when its rendered
  • allow an evaluate function to be passed into a form field ( i.e useField ). Currently this is not possible because evaluate re computes props to be passed to the component using useField but it would be nice to not have to call the useConditional hook.
  • move the rest of the schema docs over to the new docs ( I simply have not had the time to do this )
  • Types ... I know, I know the types kinda suck. I personally dont use typescript so I have had to rely on other TS wizard devs to help maintain the types.

informed's People

Contributors

adrianocola avatar alakhote avatar alexandrtovmach avatar behzadmehrabi avatar eltociear avatar fescreen avatar githubanuja avatar glebtv avatar jacargentina avatar jaffparker avatar jakobo avatar jaredhan418 avatar jkaravakis avatar joepuzzo avatar kevinhewson avatar lalezaris avatar larrybotha avatar mattbasta avatar mfp22 avatar mikejestes avatar nggonzalez avatar olleolleolle avatar ollyhodgson avatar pangoraw avatar patrickfatrick avatar peternoordijk avatar pleeko avatar robin-ambachtsheer avatar tgaeta avatar vfonic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

informed's Issues

Add .prettierrc for consistent JS formatting

PrettierJs is awesome. My default Prettier config is different from your JS style.

Keen on me adding a .prettierrc that conforms to your style so that all contributors using Prettier will contribute consistent to how you format JS?

Reusable of existing code: lodash/defaultTo

Hi. I know, you hate me ;)

You are use !value && value !== 0 ? '' : value expression to avoid of undefined or null test in input. But, the defaultTo(value, '') from lodash return the same result but it looks clearer.

Lodash is the big library but you can import only import defaultTo from "lodash/defaultTo";. Then JS code after minification will be contain only code from lodash/defaultTo.js, not whole lodash.

You thought about it?

TypeError: Cannot read property 'replace' of undefined using SSR

Running version 1.3.8

Getting this error when I try to render the simple example from the README via SSR .
@jangerhofer have you may encountered something like this when testing after #13 ?

{ TypeError: Cannot read property 'replace' of undefined
    at makePathArray (node_modules/informed/dist/index.js:1:23304)
    at ObjectMap.get (node_modules/informed/dist/index.js:1:25157)
    at Object.FormController.r.getValue (node_modules/informed/dist/index.js:1:28107)
    at buildFieldState (node_modules/informed/dist/index.js:1:4729)
    at new _class (node_modules/informed/dist/index.js:1:5858)
    at processChild (node_modules/react-dom/cjs/react-dom-server.node.development.js:2095:14)
    at resolve (node_modules/react-dom/cjs/react-dom-server.node.development.js:2061:5)
    at ReactDOMServerRenderer.render (node_modules/react-dom/cjs/react-dom-server.node.development.js:2380:22)

Thanks for the great work

How to integrate with a redux app

Use case

  • Having an react redux app, with some object, holding a string property path
  • Having a form for editing that objects path property
  • Having a widget for navigating folders on disk, and dispatch a redux action to select a given path.

Is that possible with the current version?

How do you make custom inputs?

I'm using the Material UI library and want to make inputs that work with this library that look like the material ones using their components. I don't understand how to make custom inputs even after diving into the code to figure out how the ones that are shipped with it are created.

I keep getting an undefined error at replace and I don't know why

I've tried setting up my input super basic to try to figure out where this error is coming from and I can't.

import React, { Component } from 'react'
import { asField } from 'informed';

class CustomInput extends Component {
  render() {
    console.log(this.props)
    const { fieldApi, fieldState, ...props } = this.props;
    const {
      value
    } = fieldState;
    const {
      setValue,
      setTouched
    } = fieldApi;
    const {
      onChange,
      onBlur,
      forwardedRef,
      ...rest
    } = props
    return (
      <input
          {...rest}
          ref={forwardedRef}
          value={!value && value !== 0 ? '' : value}
          onChange={e => {
            setValue(e.target.value)
            if (onChange) {
              onChange(e)
            }
          }}
          onBlur={e => {
            setTouched()
            if (onBlur) {
              onBlur(e)
            }
          }}
      />
    );
  }
}

export default asField(CustomInput);

This is basically just the same component as the Text component but I'm getting a Cannot read property 'replace' of undefined error

Could use some help with this and also maybe an update to the docs on how one might actually build their own custom informed inputs

HOC for reusable codes from Text, TextArea etc.

You have:

const Text = ( { fieldApi, fieldState, ...props  } ) => {
  const {
    value
  } = fieldState;
  const {
    setValue,
    setTouched
  } = fieldApi;
  const {
    onChange,
    onBlur,
    forwardedRef,
    ...rest
  } = props
  return (
      <input
          {...rest}
          ref={forwardedRef}
          value={!value && value !== 0 ? '' : value}
          onChange={e => {
            setValue(e.target.value)
            if (onChange) {
              onChange(e)
            }
          }}
          onBlur={e => {
            setTouched()
            if (onBlur) {
              onBlur(e)
            }
          }}
      />
    )
};

export default asField(Text);

But when you refactor it to kind of:

export const asText = (Input) => (
  ( { fieldApi, fieldState, ...props  } ) => {
    const {
      value
    } = fieldState;
    const {
      setValue,
      setTouched
    } = fieldApi;
    const {
      onChange,
      onBlur,
      forwardedRef,
      ...rest
    } = props
    return (
      <Input
        {...rest}
        ref={forwardedRef}
        value={value}
        onChange={e => {
          setValue(e)
          if (onChange) {
            onChange(e)
          }
        }}
        onBlur={e => {
          setTouched()
          if (onBlur) {
            onBlur(e)
          }
        }}
      />
    )
  }
);

const DefaultText = ({value, onChange, onBlur, ...rest}) => (
  <input
    {...rest}
    value={!value && value !== 0 ? '' : value}
    onChange={e => {
      onChange(e.target.value)
    }}
    onBlur={e => {
      onBlur(e)
    }}
  />
);

export default asField(asText(DefaultText));

It will be easy to implement custom input wrapped by asField and with reuse of code. It is supplement to #25. In #25 we can implement custom input with reuse of code in layer above the binding of form state. In this issue we can implement custom input in layer below the binding of form state.

So we get these layers:

  1. asField layer.
  2. #25 custom input layer.
  3. fieldApi/fieldState binding layer.
  4. This issue custom input layer.
  5. Raw HTML element layer.

Dynamic fields - bug when field props changed

During writing example for #49 I saw bug when field is not recreating but only field name is changed.

Simple example: https://dzwiedziu-nkg.github.io/test-react-informed/
Source: https://github.com/dzwiedziu-nkg/test-react-informed

Bug: field store old value when field props changed.

It happen when we use conditional renders i.e. my example. React use Virtual DOM. Before unomunt and mount new component's instances try to update only props. React create new instance or discard component only when there's no other way.

FormAPI.setValue for nested value

I am trying to set a value under a <Scope scope="parent">...</Scope>. The form values look like so:

const values = {
  parent: {
    child: 'SET THIS VALUE',
  },
};

But neither formApi.setValue('parent.child', 'NEXT VALUE') nor formApi.setValue(['parent', 'child'], 'NEXT VALUE') work.

Do I really have to use formApi.setValues and merge?

Async validation

Hi Joe, do you have any intentions of adding async validation into informed?

Why HOC only?

Hey, I must say I am bit confounded right now. I am using the react-form in a middle-sized project and it works quite well for its needs. Finding out today that it has been deprecated was surprising at least and I was at least hoping that Informed would be very similar. It's kinda sad realization it's mainly HOC based. This was the main reason why I haven't picked other solutions.

What is the logic behind such a decision? I mean you have the render prop approach for the Form state, but everything else must go through HOC. Why? Internally it's so much easier to have to render prop component being exposed as HOC. It's rather cumbersome in an opposite way.

It's not like HOC are bad, I am not saying that but why issuing such a constraint when it's easy not to? Please reconsider this decision. I am not sure what other solutions are out there, but I don't want to end up writing my own form library because this ends up another HOC only solution.

PS: I do like that you got rid of Redux, it was wildly unnecessary and harder to debug. Although there is still redux in keywords in package.json :)

Not seeing initialValue applied until after interacting with the input

I'm using the Text input with an initialValue, however, the input initializes with no value (empty) and then once I interact with it (click on then, click off) the initial value is applied. Do I need to define this initialValue somewhere else than just on the element.

<Text field="address" id="form-address" initialValue="John" />

Updating formState.touched on radio button

Awesome library that you've written. Just ran into a small bug.

When I'm keyboarding through the form and tab to a radio group, if I hit the space bar to select the current highlighted radio button, the formState.touched is not updated. If I use an arrow key to highlight a different radio button instead, then the touched object is correctly updated. I did notice however, that the radio group's validation is correctly ran and the formState.values object is correctly updated.

The reason I need the touched object is because I'm currently using the formState.touched to determine if the user has at least tried each form element before I enable the submit button.

Thanks for your time.

does setValues work with arrays?

I'm not seeing it working when doing something like

this.props.formApi.setValues({ someArray: [{ url: 'test'}]})

specifically the someArray element doesnt appear when I log this.props.formState.values, however the rest of the keys exist

'window is not defined' using SSR

I'm trying to migrate from react-form to informed using next.js, but it throws the following error:

window is not defined

ReferenceError: window is not defined
    at Object.<anonymous> (/myproject/node_modules/informed/dist/index.js:1:271)

The webpack config probably adds the window reference. Any way to change it to make it would work with server side rendering? Or maybe I'm missing something?

Allow stopping propagation of form submit event

Hello. Thanks for the nice library. I would like to mention a problem I'm having and two possible solutions I've come up with. I can implement either and submit a pull request if either seem acceptable. I have a form containing a modal which itself contains a form. Nested forms are disallowed by HTML so I'm using a React portal to render the modal outside the form even though it is semantically a child of the parent form. This works fine. Unfortunately, submitting the child form also submits the parent form because the submit event bubbles upwards in React and I don't have control over this.

One way to solve this is to pass the event as a second argument to the Form onSubmit callback prop. This would allow the user to call event.stopPropagation() manually.

Another way to solve this is to use the same pattern as the Form dontPreventDefault prop. A new boolean prop called something like stopPropagation that, when set to true, would automatically call event.stopPropagation() just before or after the event.preventDefault() call.

Do either of these sound reasonable?

validateOnMount?

Is there a way to have the form run validation on mount? Previously could set a validateOnMount property of the form to true.

Nested Scopes are not handled properly

When Scopes are nested, the fields inner scope's fields become unusable and throw a console error: Cannot read property config of undefined whenever you interact with them.

How to incorporate input type "hidden"?

In my application I user <Scope> to group multiple fields. These fields belong to a dataset that also as an ID. I need to pass this ID along with the scoped group. But unfortunately fields with type="hidden" are not parsed by informed.

Can you please explain how to handle this?

Why no wrapped Text, TextArea, Checkbox, Radio, RadioGroup and Select is not exported?

I want to implement custom input. It may be very helpful if the no wrapped version of Text and etc. will be exported. Then I will be able to reuse no wrapped Text and implement custom input as kind of:

const CustomInput = ({ fieldApi, fieldState, customProp, ...props  }) => {
  return (
    <div className={fieldState.error ? 'error' : ''}>
    {/*...*/}
      <NoWrappedText {...props} fieldApi={fieldState} fieldState={fieldState} />
    {/*...*/}
    </div>
  )
}

export default asField(CustomInput);

Dynamically adjust validateOn* props for a field

My use case is that I would like to validate an input on change, but only once the user has entered data in the field and blurred it (dirty and touched). So the flow would be:

User enters page, input hasn't been touched
User clicks input, enters invalid text, error doesn't appear until user has blurred input
User clicks away, Input is now touched and error appears
User clicks on input again, modifies entered data and makes it valid. Input error state should be cleared the moment the entered data is valid (not on blur)

As far as I can tell, the only way to achieve this is to somehow adjust the value of validateOnChange from false (before the input is touched) to true (after the input has been touched)

I tried to accomplish this by creating a chain of HOCs that wrap the asField HOC to change the validateOnChange prop before its passed in, using the withFieldState HOC at a higher level to determine whether the field has been touched yet. However, this creates some crazy recursion loop and crashes the page so I don't think I should be doing that.

Let me know if I'm missing something obvious here..

Edit
I think I managed to get it working with the HOC chain using something like this:

const withValidation = (Component) => {
  // Validate on change after the field has been interacted with the first time
  return (props) => {
    const {touched, pristine, ...rest} = props
    return <Component {...rest} validateOnBlur validateOnChange={touched && !pristine}/>
  }
}

const withInputFieldState = (Component) => {
  return withFormState((props) => {
    const {formState, ...rest} = props
    const touched = formState.touched[props.field]
    const pristine = formState.pristine[props.field]
    return <Component {...rest} touched={touched} pristine={pristine}/>
  })
}

export default withInputFieldState(withValidation(asField(Text)))

It seems fairly ugly though

Force entire form to re-validate?

For example, I have a reset password page with:
New Password
Confirm Password

And the inputs sort of need to depend on each other, so that when I change one the other one becomes invalid if they no longer match.

What I'm thinking is adding a "validate" function to the form API that will just force validation check to occur on all inputs in the form.

Setting values on the form later?

In the react-form I had a component like this which essentially loads data asynchronously while the form is being already displayed empty giving better UX. Would it be too problematic to have values prop in Informed as well which would override the state completely on every update?

export class FormLoader extends React.Component {
  state = {}
  async componentDidMount() {
    if (!this.props.shouldLoad) {
      return
    }
    const values = await this.props.loader()
    this.setState({ values })
  }
  render() {
    const { shouldLoad, loader, ...props } = this.props
    return <Form values={this.state.values} {...props} />
  }
}

Access formApi from asField() ?

Building completely custom inputs with asField, is it possible to access the formApi? I would like to only check submitted (in addition to touched) before applying the error state.

Error When Using Array for formApi.getValue()

In the previous library, I could retrieve a nested value by passing the path array to the formApi.getValue() method. However, when using it via informed, I'm getting the following error:

Uncaught TypeError: e.replace is not a function

While debugging I noticed that when you build the array from the string equivalent, prop.nested, there is no check to see if the array is already there: https://github.com/joepuzzo/informed/blob/master/src/ObjectMap/index.js#L74

It might be useful to see if this is already present to allow variously data formats for lookup.

Need feature: the "changed" state

Currently you have value and touched. value is the current value (typed or achieved from initialValues). touched is set to true if input was focused and lost focus. I want to mark inputs who are changed value according to initial value. Typical application: edit form.

In react-form I can implement it in my custom input because I can achieve access to defaultValues via:

contextTypes = {
  formProps: PropTypes.object
}

And this.context.defaultValues['my_field_name'] have my initial value. So in my custom input I can compare value from defaultValues with value from field state and mark my custom input as changed.

In informed the initialValues is stored in controller but controller is used in bindToField and no propagated to Field. Moreover nor withController and not FormContext is not exported. So I have not access to controller from my custom field.

Can you implement access to initialValue from formApi/fieldApi or formState/fieldState or better: implement changed state next to touched state?

React error upon using <Select> component

Uncaught Error: Element ref was specified as a string (select) but no owner was set. This could happen for one of the following reasons:

Element ref was specified as a string (select) but no owner was set. This could happen for one of the following reasons:
1. You may be adding a ref to a functional component
2. You may be adding a ref to a component that was not created inside a component's render method
3. You have multiple copies of React loaded
See https://fb.me/react-refs-must-have-owner for more information.

The values passed to onSubmit aren't immutable

Reproduction steps:

  1. View: https://codesandbox.io/s/53y9kroy8n
  2. Enter data into the text area
  3. Click 'Submit', the submitted value should be populated
  4. Click 'Toggle visibility' twice.

Expected result:
I would expect the object this.value to not change, since only the internal state of the form that has changed, and onSubmit has only been run once.

Observed result:
this.value is cleared.

It looks like the value passed to the onSubmit prop of the form isn't a clone of the values, it's the actual values themselves. When the values are cleared internally, they have also been cleared anywhere they are used directly.

A workaround is to deep-clone the values passed to the onSubmit prop, but I would expect informed to handle that for me.

Form component onChange function is triggered twice

The function is being fired twice, once with the form state, as i would expect, and a second time with the event from the input's onChange function.
image

Here is a minimal version of my form
image

Is this intended behavior? I would be unintuitive to have to try to filter out the second function call.

setValue(null) removes the value from the state

Hey,

when using setValue from the <Field /> perspective and setting it to null, the next state is without that value instead of having it as null.

const initialState = {
  some: 'value',
};

Then: <Field field="some" />.setValue(null), we get the following state:

const nextState = {};

In my use case null and undefined is VERY different, so I'd need the value to have the null set...

[Missing dependency] @babel/runtime

I use informed in a typescript project and the compilation failed without adding @babel/runtime manually. Can you add it to the package.json ?

Props changed don't affect fieldState

The main purpose of a React component is that "when its internal and external state (props) change, its content must change (or not)".
But that's not the case for the inputs. For example, I use a Text component and validate if it's empty, but I don't want it to until I submit, okay so far, but I want that after submitting the form, if there was an error, the component should validate on Change, so I can do a proper validation, so I tag it with validateOnChange, but when it changes, nothing happens, just when I submit again the form, it revalidates.

I guess a simple check on componentDidUpdate reflecting changes to the fieldState would solve the issue.

As a workaround, the following code achieves (closely) the desired behavior.

<Text
    field={"name"}
    validate={validation}
    placeholder={"Name"}
    initialValue={formState.values['name']}
    validateOnMount={this.state.formSent}
    validateOnChange={this.state.formSent}
    key={`name-${this.state.formSent|0}`}
/>

Assuming you have a state flag formSent changing with the Form prop onSubmitFailure. The key prop is used to force the component to remount, which is not the perfect behavior, but solves the problem, but as it's a brand new component you should set validateOnMount so it validates right after the first submit.

EDIT: Another downside about this, is that you have to set the initialValue of the field according to the formState, so it doesn't empty whenever it remounts, and it would affect other behaviors

As of now, I'm a bit busy so I couldn't make a PR nor avaliate a change in the source code. If you could please take a look, I don't think it would be something too expensive.

Cannot read property 'config' of undefined without proper <Field />

First of all, I am glad that you took the time to rewrite the react-form!

I found react-form concepts really useful, but because it was too bloated I was about to write my own implementation which would look EXACTLY like informed. Credits for perfect timing! 😆

Anyway, back to issue. If the <Form /> looks like this:

<Form
  getApi={(formApi) => formApi.setValue('str', 'gni')}
>
  <Text field="str" />
  <button type="submit">Submit</button>
</Form>

Calling formApi.setValue(key, value) I get this error after witch the complete tree unmounts:

Cannot read property 'config' of undefined

Its pointing to this line:
https://github.com/joepuzzo/informed/blob/9a68ff6b45555566ec5f38592a64eef0080f5cff/src/Controller/FormController.js#L87

I figured that you cannot set a value when there is no initialized <Field /> representing that key.

Is this intentional? Maybe setting values should work even without the field component? What do you think?

Best,
Denis

[Typescript] declaration file missing

I don't find informed definition file in the provided package and @types/informed doesn't exist yet. Could you migrate @types/react-form with the changes ?

Value renderer/parser

I propose to implement customizable fieldState.value -> html's input value function and e.target.value -> fieldApi.setValue function. It can be done by:

const Text = ( { fieldApi, fieldState, valueParse, valueFormat, ...props  } ) => {
  const {
    value
  } = fieldState;
  const {
    setValue,
    setTouched
  } = fieldApi;
  const {
    onChange,
    onBlur,
    forwardedRef,
    ...rest
  } = props
  return (
      <input
          {...rest}
          ref={forwardedRef}
          value={valueFormat(!value && value !== 0 ? '' : value)}
          onChange={e => {
            setValue(valueParse(e.target.value))
            if (onChange) {
              onChange(e)
            }
          }}
          onBlur={e => {
            setTouched()
            if (onBlur) {
              onBlur(e)
            }
          }}
      />
    )
};

Text.defaultProps = {
  valueFormat: value => value,
  valueParse: value => value
}

It will be very helpful to implement i.e. number input with round to given max decimal places.

React optimization: why you use lambda expressions?

When you have:

const Text = ( { fieldApi, fieldState, ...props  } ) => {
  const {
    value
  } = fieldState;
  const {
    setValue,
    setTouched
  } = fieldApi;
  const {
    onChange,
    onBlur,
    forwardedRef,
    ...rest
  } = props
  return (
      <input
          {...rest}
          ref={forwardedRef}
          value={!value && value !== 0 ? '' : value}
          onChange={e => {
            setValue(e.target.value)
            if (onChange) {
              onChange(e)
            }
          }}
          onBlur={e => {
            setTouched()
            if (onBlur) {
              onBlur(e)
            }
          }}
      />
    )
};

export default asField(Text);

The onChange and onBlur props will be recreated always when render() will be executed. So the children component will be unnecessary rerendered. See: https://reactjs.org/docs/optimizing-performance.html#examples

I propose to refactor to:

export class BasicText extends React.PureComponent {
  onChange = (e) => {
    const {fieldApi, onChange} = this.props;
    const {setValue} = fieldApi;

    setValue(e.target.value);
    if (onChange) {
      onChange(e.target.value, e);
    }
  };

  onBlur = (e) => {
    const {fieldApi, onBlur} = this.props;
    const {setTouched} = fieldApi;

    setTouched();
    if (onBlur) {
      onBlur(e);
    }
  };

  render() {
    const {fieldApi, fieldState, onChange, onBlur, ...rest} = this.props;
    const {value} = fieldState;

    return (
      <Input
        {...rest}
        value={value}
        onChange={this.onChange}
        onBlur={this.onBlur}
      />
    );
  }
};

export const Text = asField(BasicText); // BasicText - exported too, unwrapped Text component from #25 :)

Need export makePathArray in npm package

Hi,

You have many great functions and classes. Why is not published in npm package?

I would be happy:

  • makePathArray
  • ObjectMap
  • context's from informed/src/Context/index.js

Its will be very helpful to implement self custom fields.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.