New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
redux-form v6 API Proposal #726
Comments
Wow, big changes! Thanks for putting such a detailed proposal together. I'll have to have a proper think about how this would impact the way I've been building forms with The formatters thing is definitely a pretty compelling proposal, done this myself recently in a much less elegant way :) (although you might be talking about something subtly different, mine is formatting as you type e.g. inserting spaces in a credit card number, rather than formatting after you blur) |
So one thing that I noticed is that I sometimes want to manually call the
How would we go about this with the v5 API? Dispatching redux-form change actions instead? |
This is a pretty big deviation from the API, no? It's looking a lot more like React-Redux-Form. Much more similarities. |
@tomduncalf Excellent point. The field objects (components) should have methods on them to dispatch actions. Perhaps the const { fields: { firstName } } = this.props
return (
<form>
<firstName.text/>
<button onClick={() => firstName.change('Tom')}>Set name to Tom</button>
</form>
) @davidkpiano Yes, your library was very inspiring. As was the entire discussion on #529. |
The idea of having the Wondering if you have any "finger in the air" guesses at a rough timeline for these changes? (e.g. 1/3/6 months kind of level) |
@tomduncalf Ooh, I like the "props available as migration step" idea. 👍 I'd say 1-3 months. |
@davidkpiano Yeah, it's pretty much all dictated as a result of point 1. |
Great great work on the proposal @erikras! 🎉 I also have a pretty big app using |
I will say that a lot of developers might have an aversion to the "convenience inputs" such as Why not try something more succinct, like |
Yes, using strings is another very real possibility. Good feedback. |
In retrospect, for RRF, I would have done something like <Field model="user.choices[]">
<input type="checkbox" value="foo" />
<input type="checkbox" value="bar" />
<input type="checkbox" value="baz" />
</Field> |
All these changes are really exciting and I'm grateful to you to give the community a chance to share its ideas. I would like to share some details about how we use We are using So, what can I say about this API proposal? tl;dr Strings, meh! Objects, yeah! Disclaimer: I hate JSX (and a lot of people do too).
I totally agree here. We had to create complex wrappers to deal with performance issues on large forms (somewhat what is proposed in 2.1. Moreover, I prefer composing
This is great. Really. This is how we create forms in our application. But I'm not sure about the
We tried to solve these issues using the // JSX-friendly approach but requires code instrumentation using `context` or cloning children.
<Field name="picture">
<ImageInput {...fields.picture} />
</Field>
// Less JSX-friendly approach.
// Instrument `field` and pass it down.
<Field name="picture">
field => <ImageInput {...field} />
</Field>
// Fortunately it is awesome using component factories!
field({name: "picture"}, imageInput)
field({name: "picture"}, field => imageInput({...field, otherProp: "foo"}))
This can be hard to manage if a form declares nested dynamic fields (I know, In my opinion, the // Types
type FieldRef = {
$$typeof: "ref"
path: string
}
type FieldMap = {
$$typeof: "map"
[field: string]: FieldRef | FieldMap | FieldSet
}
type FieldSet = {
$$typeof: "set"
[index: number]: FieldRef | FieldMap
}
// Sample dump
{
$$typeof: "map",
name: {$$typeof: "ref", path: "name"},
dot: {
$$typeof: "map",
and: {
$$typeof: "set",
0: {$$typeof: "ref", path: "dot.and[0]"},
1: {
$$typeof: "map",
"bracket": {$$typeof: "ref", path: "dot.and[1].bracket"},
},
}
},
} Doing this, every change does not trigger a render since
While it's a good idea for JSX codebase, it's a disaster for non-JSX ones. function UserInfoForm({fields: {name, age, preferences}}) {
return (
DOM.form(null,
React.createElement(name, {component: "input", type: "text"}),
React.createElement(age, {component: "input", type: "number"}),
React.createElement(preferences.favoriteColor, {component: "select"},
DOM.option({value: "#f00"}, "Red"),
DOM.option({value: "#0f0"}, "Green"),
DOM.option({value: "#00f"}, "Blue"),
)
)
)
} If function UserInfoForm({fields}) {
return (
DOM.form(null,
fields.name({component: "input", type: "text"}),
fields.age({component: "input", type: "number"}),
fields.preferences.favoriteColor({component: "select"},
DOM.option({value: "#f00"}, "Red"),
DOM.option({value: "#0f0"}, "Green"),
DOM.option({value: "#00f"}, "Blue"),
)
)
)
} Please notice that I added quotes around
While I must admit this may be useful, in large codebase this is pretty useless, furthermore if you need to style components and always specify new props. Think about what problems does redux-form try to solve? In my opinion,
In Redux philosophy, reducers only apply mutations. Validation should belong to the middleware. If you treat sync and async validation the same way using
It belongs to user-land in my opinion.
Moving this to form-level or field level would be a great move. Having to declare normalizers outside of the form makes code-splitting (and chunk loading) a pain to deal with.
This is awesome 👍
Currently it's impossible to add errors to a collection of fields. We can add an error to
Be careful, you cannot hold refs on functional components. Quote React documentation:
|
@ooflorent Wow! Excellent analysis. Thank you for pushing back on some API overreach, e.g. debounce. This is exactly the sort of well-reasoned feedback that I was hoping for.
You may be right that I may be stepping too far into the realm of "convenience" and maybe It never occurred to me that people might be using Was
It's certainly easy enough to import { DOM: { input, select } } from 'react' Thanks again for contributing to this discussion. Everything is still a work in progress. |
@erikras My tl;dr was referring to |
@ooflorent Why? It's basically the only way Redux-Form can know where to both get and set the value. Think of it as a simpler implementation of a Lens in functional programming. |
@davidkpiano I don't see how the first example is cleaner than the second: /* 1 */ <Field name="foo.bar[1].baz" />
/* 2 */ <Field name={foo.bar[1].baz} />
|
Missing the point. You're sending a nonsense value to the |
If |
@ooflorent You're both talking past each other. I think what @ooflorent is suggesting is with the fields array provided. const { fields: { foo } } = this.props
return (
<form>
<Field {...foo.bar[1].baz} component={input} type="text"/> // either this
<Field config={foo.bar[1].baz} component={input} type="text"/> // or this
</form>
) ? |
@davidkpiano The |
This may be an ignorant suggestion, but what about wrapping the return value of render to wrap the fields for you? e.g.
the spread operation
|
@erikras can you expand on section Currently, I don't see any way to have both a sync validate function and custom submit function exist as component instance methods. The motivation is so that validate and submit both have access to component instance props via the same means. Ideally, I would like to be able to: export default class MyForm extends Component {
validate (values) {
// use this.props
}
submit (values, dispatch) {
// use this.props
}
render () {
const { handleSubmit } = this.props;
return (<form onSubmit={handleSubmit(::this.submit)}>...</form>);
}
} Currently validate has to exist outside the component, and even though it is passed props, it would be nice if custom validate() and custom submit() could be provided in a consistent manner. If my expected usage scenario is misguided, please let me know. |
@erikras Wow those are quite significant changes. I've recently run into issues regarding the performance where the whole form re-rendered, so I like the idea of having the fields be connected instead of the form container. You've already mentioned this, but for me a very important part is that components like One thing I didn't see in the spec above is how you want to handle submitting a form and storing the returned errors. Right now we can tunnel the errors using a rejected promise. Are you planning something similar for v5? If not, how are you planning to handle this? |
@ralphschindler Another option, if we wanted to keep the "validate whole record" approach to sync validation would be to have a non-rendering component that could be popped optionally into the form to specify a validation function. That component would be connected to the form values, so it would be rerendered (though result in no output) on every value change. Something like: import {reduxForm, SyncValidation} from 'redux-form'
@reduxForm({ ...config })
export default class MyForm extends Component {
validate (values) {
// use this.props
}
submit (values, dispatch) {
// use this.props
}
render () {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit(::this.submit)}>
... // inputs
<SyncValidation validate={::this.validate}/>
</form>
);
}
} Is that disgusting? @johanneslumpe Yes, upgrade path is important. We have to figure out where we want to go first, and then we'll figure out how to get there. 😄 Because of the significance of this change, every input will have to be wrapped in a |
@erikras I agree. I' not sure, but maybe we could have a codemod for that? Although I believe that it might be huge effort to write one which fits all cases and doesn't arbitrarily break code. Still, tedious !== hard. So the more straightforward we can make it the better :) |
👍 I like that option. My primary motivation is having a consistent extension story, and using That said, I also like the concept of per-field, at-submit-time validation, and would be interested in you providing usage scenarios for that under 4.2 to better follow along and provide example to build off of (I am not sure I follow the request in #254, but I do like the idea of using HTML5 validation). |
Re: |
@tomduncalf Right, you're the one doing client-side async validation on submit, which isn't supported very well at the moment. That should be an additional requirement. |
Not to (again) echo an earlier comment, but is there any consideration between keeping the current api as "redux-form", and releasing this new proposal as a separate package, perhaps titled "redux-complex-form"? |
Agree, let's make small steps (like React does) to achieve our goals without breaking lots of code, const reducer = combineReducers({
form: formReducer({
addons: {
normalize: require('redux-form-normalize'),
components: require('redux-form-components'),
'format-parse': require('redux-form-format-parse')
}
})
});
// -----
@reduxForm({
addons: [
'format-parse', // will add format and parse methods to every form field
'normalize', // will add normalize method to every form field
'components', // will add new fancy component creators to props,
(state, action) => {
if (action.type === 'CHANGE') {
// ... do something
}
return state;
}
]
}) So basically after every action ( |
This issue/discussion is amazing. I can't believe I'm only now stumbling onto it. I'll look forward to seeing how I can contribute to v6.x+! Thank you [again] @erikras for all your hard work on this. It is greatly appreciated! |
Hey guys. Just wanted to let you know It doesn't contain all of the things in this proposal (many of them were not technically possible), but it gets a lot of them. |
Amazing! Been waiting for this because I am just getting into a new project, so I will be taking a look first thing tomorrow to start giving you feedback. Thanks @erikras. |
@erikras Thanks for pointing out the release. I'm looking through the examples, particularly the synchronous validation example and I still can't see how form-wide elements are able to subscribe to form-wide errors. For example, I want my submit button to be disabled unless synchronous validation is passed. Am I missing how to do it with v6, or is it still being worked on? |
@morgante You still have many of the same props as earlier versions in your form component, including |
@AubreyHewes Yeah, React 15 really threw a kink in my release pipeline. Is this better? |
@Erikas cheers, works for me ;-) |
@erikras great! but the part with sync validation made me sad, Is it possible to dynamically load validators to reducer? // AppFormReducer.js
import { reducer } from 'redux-form';
export const formReducer = reducer.lazyReducer();
// App.js
import { combineReducers } from 'redux'
import { formReducer } from './AppFormReducer'
const reducer = combineReducers({
form: AppFormReducer
});
// ./components/MyForm.js
import { reduxForm } from 'redux-form';
import { formReducer } from '../AppFormReducer'
formReducer.registerValidator('myForm', (values, props) => {
// do validations
return errors;
});
reduxForm({
name: 'myForm'
})(MyForm) |
@erikras we want to give you a little financial support for putting so much effort into this project... how do we do that. |
@andrewmclagan Thanks. The easiest way is with the "donate" link (it's disguised as one of the badges) at the top of the README. |
@umidbekkarimov Okay. There is a way to support the old way of passing in validation, but it requires refreshing the entire form when any sync validation error changes, so I made the decision to not provide that, but I will reconsider allowing that. It can be hard to strike a balance between being feature-full and accommodating everyone's needs and trying to keep a small API surface area for clarity. Thank you for your comment. |
@erikras: This seems like a bug. If you manually set an error object with
( |
@erikras Thank You! it was the killer feature why I chose |
Thank you @erikras for this enormous rewrite of |
ASAP. Maybe a week or two? Kind of hard to block out man hours for OSS work. |
Yes, agree so very very appreciative of your efforts. Hopefully our team wil have time in the near future to contribute |
Excelent API changes, very happy with them, good job |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
IMPORTANT This post is outdated, but I will leave it here for posterity.
React v15 was released before I could get the next rewrite of
redux-form
published, so I had to releasev5
as the Controlled Inputs update. The big rewrite will now bev6
, but references to it below talk aboutv5
.The initial alpha version of this proposal has been released:
April 20, 2016 -
v6.0.0-alpha.4
ReleaseGreetings all,
There are a number of feature requests, bugs, and performance issues that have caused me to rethink the design of
redux-form
. I'd like to use this issue as a way to introduce you to some of my thinking, prepare you for what's to come, and get your feedback. Hopefully at least one of the features will make you excited to migrate tov5
when it comes out.redux-form
v5
API ProposalGlossary
1. The form should not be connected
When writing the initial API, I provided things that seemed convenient for small forms, like having
this.props.values
be the current state of the values of your form. This required having the wrapping form component to be connected to the Redux store, _and to re-render on every value change_. This doesn't matter if it's just ausername
andpassword
form, but some of you are writing complex CRM systems with potentially hundreds of fields on the page at a time, causingredux-form
to grind to a halt. #529So, what does this mean?
mapStateToProps
andmapDispatchToProps
is going away. You'll have to useconnect()
directly yourself.2. Each field is connected
Each field should subscribe only to the very specific slice of the Redux state that it needs to know how to render itself. This means that
redux-form
can no longer only provide you with props to give to your own inputs.So what do we do? The destructured props was one of the most elegant things about
redux-form
!There are three options that I want to support:
2.1. On-Demand Field Structure
The
redux-form
user base seems pretty divided on how much they like providing the list of fields for the entire form, so I want to provide away to arbitrarily specify the path to your field at render-time. This can be done with a<Field/>
component that is given the name of the field indot.and[1].bracket
syntax.2.1.1. Custom Input Components
Another key feature of
redux-form
is the ability to very simply attach custom input components. Let's imagine a hypothetical input component calledSpecialGeolocation
that requires the following props:Let's further complicate the matter by requiring that we save our location in a
{ latitude, longitude }
json object. How would we use this component inredux-form
v4.x
andv5
?Well, given the extra string-to-json requirement, we need a complimentary pair of
format
andparse
functions. We need these in _both_v4.x
andv5
!In
v4.x
we map the props manually......and in
v5
we have to specify amapProps
function to do it.Simple enough, don't you think?
2.2. Provided field structure
The benefit of providing your fields array as a config parameter or prop to your decorated component is that
redux-form
, as it does inv4.x
, can provide you with the connected field components as props. So, whereas thethis.props.fields
provided tov4.x
were the props to your input components (value
,onChange
, etc.), inv5
they are now theField
components (see 2.1 above) which will apply those props to an input component of your choosing.2.2.1. Custom Components
You can do the same thing as described in 2.1.1 above with these fields.
2.3. Convenience Inputs
All that
component={React.DOM.whatever}
boilerplate is pretty ugly. Inv5
, I want to provide convenience properties. These will be provided using memoized getters, so they will only be constructed if you use them. The use of getters means saying a not-so-fond farewell to IE8 support. React is dropping its support, and this library will as well.Look how concise and elegant this is:
All of the types of
<input/>
will be supported as well as<select>
and<textarea>
. These will be able to handle special quirks about how checkboxes or radio buttons interact with data.2.3.1. Submit and Reset buttons
Just like the fields,
this.props.submit
andthis.props.reset
button components will also be made available, with flags for common functionality you might expect, e.g.disableWhenInvalid
. More on form submission below.3. Displaying Errors
Also as a getter on the field objects, like in 2.3 above, there will be an
error
component that will output a<div>
with the error message.4. Sync Validation
The move in
v5
to decentralize the power from the outer form element to the fields themselves proves a problem for howredux-form
has historically done sync validation, as the form component cannot rerender with every value change.4.1. Sync Validate Entire Record
Traditionally, your sync validation function has been given the entire record. I would still like to have a "validate this entire record" functionality, but it is going to have to move to the reducer, and the reducer will store the sync validation errors in the Redux state.
4.2. Sync Validate Individual Fields
Many
redux-form
users over its lifetime have requested the ability to putrequired
ormaxLength
validation props directly on the input components. Now theredux-form
is controlling the field components, it makes perfect sense to do this.v5
will attempt to use the same props as defined in the HTML5 Validity spec, as well as to set the errors directly onto the DOM nodes withsetCustomValidity()
as defined by said spec. #254.5. Async Validation
For async validation, you will have to specify which fields will need to be sent to the async validation function so that
redux-form
can create a non-rendering connected component to listen for when those fields change, similar toasyncBlurFields
works inv4.x
, except that the async validation function will only receive the fields specified, not the whole record.5.1. Debounce
I'm not certain of the API yet, but some mechanism for allowing async validation to fire on change (after a set delay), not just on blur, will be provided. I'm open to suggestions.
6. Normalizing
Since sync validation is moving to the field components, I think it makes sense for normalizers to be there as well. Something like:
What I liked about normalizing being on the reducer in
v4
was that your value would get normalized even if it was modified from some non-field dispatch of aCHANGE
action, but I don't think that was happening very often in practice. It can certainly be added back in the future if need be.7. Formatters and Parsers
Formatters and parsers are the first cousins to normalizers. Say you want to display a phone number text field in the format
(212) 555-4321
, but you want to store the data as just numbers,2125554321
. You could write functions to do that.The formatter is taking the raw data from the store and making it pretty for display in the input, and the parser takes the pretty input value and converts it back to the ugly store data.
8. ImmutableJS
Many of you have been holding your breath so far hoping I'd get to this. 😄
I've gone back and forth and back and forth on this topic. I rewrote the whole reducer to use ImmutableJS, and the rewrote it in plain js objects several times. I'm certain of the following things:
redux-form
should NOT require that its users keep their Redux state as ImmutableJS objectsSo, in conclusion, I'm officially forbidding the use of ImmutableJS with
redux-form
. Deal with it.😜 😜 Nah, I'm just kidding. I've actually solved this problem. 😜 😜
If you do this...
...then
redux-form
will keep all its internal state in plain javascript objects, doing shallow copies up and down the tree to not mutate the objects that don't need to change (be rerendered).But, if you do this...
...then
redux-form
will keep _all its internal state_ in ImmutableJS objects.I've managed to hide the internal state structure behind a minimal
getIn
/setIn
façade enabling the same reducer code to work on both types. This has taken up most of my effort onv5
so far, but it's working beautifully with extensive tests.9. Separation of Values and Form State
As a library user you shouldn't care too much about this, but many of the problems and bugs (#628, #629, #662) that
v4.x
has suffered from have been a result of the internal state structure being like this:In
v5
, the initial values, current values, and field state will be kept separate.This will make the
RESET
action literally just do this:It will also enable things like subscribing to all the form values if one choses to. Such a decorator will be provided if only to enable the library demos.
10. Form Submission
"But!", you say, "If the form component isn't connected, how can it submit the values from the form component?"
This was a big problem with this inversion of control, but I have invented a way for a large outer component to be able to read values from the Redux store without being directly connected, and therefore rerendering on every value change. This should allow for a
handleSubmit()
paradigm similar tov4.x
, but I haven't gotten to proving that yet.11. What is going away?
I think the only functionality that going away is
formKey
andreduxMountPoint
11.1.
formKey
formKey
dates back to before you could specifyform
as a prop (it used to only be a config parameter). It was designed to allow you to use the same form shape multiple times on a page, but you can do that withform
. The only thing stopping the removal offormKey
before was normalizers, but since they are moving to the fields (see 6 above),formKey
can be laid to rest.11.2.
reduxMountPoint
reduxMountPoint
was obsoleted by the more flexiblegetFormState()
, whichv5
will still have.12. Documentation, Examples, and Website
I'm not happy with how the examples are currently implemented on the
redux-form
site. I like how the Redux examples are each in their own little folder that can be run individually. Forv5
, I want to meet the following goals for the docs:master
branch in a way that is navigable entirely on Githubmaster
branch in a way that can be run locally withnpm install
andnpm start
hashHistory
.That is not an easy requirements list to hit, but I've figured out a way.
13. Roadmap
The following will not make it into the release of
v5
, butv5
is being designed with these future features in mind.13.1. Custom Library Extensions
In the same way that the standard inputs in
React.DOM
will be provided as convenience properties (see 2.3 above),redux-form
v5
will (eventually) allow extensions to be applied via third party extension libraries to make it easy to use a third party input library. So imagine something like this:React Native is the most obvious extension, but any input library could be done. React Bootstrap, React Select, Belle, Date Pickers, Wysiwyg editors, etc. could all be done in this way.
One thing I'm not 100% sure about is whether or not extensions should be allowed to overwrite the built-in convenience components, like
text
orpassword
, or if they should be prefixed as in my example above. Opinions welcome.13.2. HTML5 Validity
It's possible that the actual
setCustomValidation()
stuff mentioned in 4.1 above will not make the initial release.Conclusion
As you can see, I've been putting some thought into this. Thank you for taking the time to read through it all. If you see any glaring mistakes or think something should be done differently, I am very open to constructive criticism. I'm pretty excited about these changes, and I hope you are, too.
The text was updated successfully, but these errors were encountered: