teslamotors / informed Goto Github PK
View Code? Open in Web Editor NEWA lightweight framework and utility for building powerful forms in React applications
Home Page: https://teslamotors.github.io/informed
License: MIT License
A lightweight framework and utility for building powerful forms in React applications
Home Page: https://teslamotors.github.io/informed
License: MIT License
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 ?
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
Hi,
You have many great functions and classes. Why is not published in npm
package?
I would be happy:
makePathArray
ObjectMap
informed/src/Context/index.js
Its will be very helpful to implement self custom fields.
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
Useful for displaying a loading indicator and greying out the submit.
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
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.
Missing e parameter on submitForm docs.
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.
I'm starting with the lib; i'm seeing this should be added for new users coming from react-form
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.
Hi @joepuzzo I've cloned the repo and tried to do npm start
I've got this
Can you add the missing files? I think I can help fixing bugs, etc. Cheers!
I use informed in a typescript project and the compilation failed without adding @babel/runtime manually. Can you add it to the package.json ?
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" />
The new async validation is very cool. Can we get any indicator in the state out of the box?
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?
Hi,
In this sample the spouse
field is dynamic.
Why you clear spouse
value when field is hide?
This makes it impossible to create complex forms, eg with tabs. Clearing value should be optional, configured in props.
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} />
}
}
asField
can be useful for creating custom inputs. It is not exported right now.
<Form className={classes.form} onSubmit={e => (isRegistration ? register(e, this.state) : login(e, this.state)) } >
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?
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
When Scope
s 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.
Hi Joe, do you have any intentions of adding async validation into informed?
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
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.
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?
Hi,
For some reason running the npm install end with downloading the dist folder with only index.js in it and the readm files
any reason?
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 :)
Reproduction steps:
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.
When using dynamic fields, the position of the field seems to be used in the identity of the field, which leads to unexpected behaviour of the fields being linked in value. Replicated in sandbox
Reversing the order of the fields in group A does not lead to the behaviour.
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?
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...
This was requested feature in react form TanStack/form#271
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 :)
Hi,
Please export BasicRadioGroup
.
BasicText
works good but BasicRadioGroup
is also needed :)
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:
asField
layer.fieldApi
/fieldState
binding layer.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?
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?
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);
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.
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?
Would help in a big way with debugging.
Perhaps something like the following:
The config I use (inspired by xstream's config): https://github.com/fixate/xstream-store-resource/blob/master/package.json#L11-L13
Keen for me to make a PR?
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.
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.
Use case
path
path
propertyIs that possible with the current version?
Is there a way to have the form run validation on mount? Previously could set a validateOnMount property of the form to true.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.