Open Syynth opened 4 years ago
Hi Ben! Thank you for your interest and you thoughts.
all fields get a unique key on being rendered. Perhaps it is a Symbol, or something else
Using symbols as a unique key for fields is interesting idea, but here we may find some pitfalls with dynamic forms where fields may be mounted and unmounted. This way the exact same field after re-mount will get a new key (symbol) and will loose connection with it's previous state
Recoil stores atoms in a ES Map object, so I experimented with your example project by trying out having the keys for fields as arrays (i.e. [formKey, fieldName, 'mounted'] and so on), and I think this might be an interesting detail to help avoid collisions and add flexibility (I don't think anybody should have / characters in their field names but you never know, people do crazy things).
We are on the very early stage right now, so I see no reason to add complexity without any benefits. Using /
in field name is so rare case so we just don't think about it at the moment. On later stages we may rework the part of code that is responsible for keys to change key signature or even type. It would be not so difficult if necessary.
The second, accompanying aspect of this is the way larger form structures would be composed.
We need to think more about it, there are some ideas in my mind how this may be achieved on both sides (in the client's code and in the library). One of the ideas is to use some kind of "smart fields". The "smart field" is a component built on top of library utilities and act like one field externally, but may contain a deep structure internally and represented as a set of fields in the UI. So from library point of view nothing should be changed to work with this field (the only difference: the value is not a primitive, but an object). But the library may provide a set of utilities to help build these "smart fields" not from the scratch but with the same ideas, patterns and code pieces that is used to build the main form logic.
Hello! I just learned about recoil (like everyone else :P) and immediately realized this is the solution I needed to implement the kinds of forms I write pretty much every day at work.
Naturally my first instinct was to go register an npm package and realized it was already taken. I looked through the basic POC you've got as of last night and it gave me some ideas about possible routes to implementation.
I've had to implement a lot of very large, very heavy forms on my (unfortunately closed source) work project and we initially started with Redux Form and moved kind of recently to a kind of hybrid wrapper around Final Form and React Final Form. While these are both pretty good solutions I've been bitten by a handful of issues related to updating large portions of form data. In these cases, especially with large arrays and/or very rich form controls rendered by the update to a top level array or similar in the form data/struct.
After some careful consideration of the issue last night I think I've identified the core issue we've dealt with across very large forms with many subscriptions, and I think it is related to the fact that the subscriptions and form information is typically stored hierarchically. This means that if you are using persistent data structures, a change propagates from a low level field all the way up to the top, and then the subscription checks/diffs have to be run and compared for all the fields in the subtree of data.
This means that many components are unnecessarily checked against some kind of memoization function, like React.memo, shouldComponentUpdate, or something internal to say, react-redux.
Stated another way, by structuring the contents of the form data, and therefore all the subscriptions to create a tree structure requires making many checks to determine if data represents an actual change.
Even in the case of recoil, if the form values were to be stored in an atom, and then a tree of selectors were used to map fields to individual portions of the form values (or validation/dirty/etc type metadata), then changes to the form state still result in a cascade of selector updates, even if they ultimately return the same value and don't make it to the react update phase.
So, my proposal is this: all fields get a unique key on being rendered. Perhaps it is a Symbol, or something else [1]. Each field stored this way will just be totally flat across the board and identified uniquely. (in reality i suspect it'll probably be more like a collection of related atoms, for field.value, field.touched, field.dirty, etc. etc. [in my head i've been referring to these groups of atoms as a 'molecule' as a bit of a joke, but maybe it conveys the idea]). The second, accompanying aspect of this is the way larger form structures would be composed. Aggregate form values, like objects, arrays, and possibly other forms (nested forms is a use case I face at work to handle re-use, but maybe it isn't widely applicable) would also get their own set of atoms, to allow validating an entire array for instance, and the 'value' of the array would be a list of ordered keys which act as pointers to either other fields/values or other aggregates like a struct (imagine a list of address objects, for example). using this to store explicit data about the aggregate as a whole, combined with selectors that maybe summarize the combined values would allow for some rich functionality that composes nicely. (another example here would be that each address struct must be validated individually, but the list might have a validation function which restricts the list to containing no duplicates. you could then imagine the validity of the list as either the sum of its items or it's own rules, or perhaps both).
By recursively combining all the values in the form this way, when handling submission you can use
useRecoilCallback
to compute this once, on demand. additionally, by using the fact that selectors support defining aset
operation, using a selector to overwrite many individual atoms at once is a convenient way to batch updates to fields. (back to the address example,addressSelector.set({ street: ..., city: ... })
all at once would be the basic idea.) I do also want to point out that I realize your current implementation for fields already uses these flat values, but obviously as it's very early I don't know what your plans for building richer structures is.I hope this wasn't too much of a brain dump but I wanted to share my thoughts before I go off rebuilding the same thing other people are already working on. If you're interested in chatting in a more real time fashion feel free to contact me on twitter @syynth or on discord @syynth#0528. If not, continuing the discussion right here is fine too. Hopefully I can clarify anything that seems a little confusing. If this isn't the direction you're looking to take, that's fine too, but I know I'll find a way to build something like this one way or another haha.
[1] Recoil stores atoms in a ES Map object, so I experimented with your example project by trying out having the keys for fields as arrays (i.e.
[formKey, fieldName, 'mounted']
and so on), and I think this might be an interesting detail to help avoid collisions and add flexibility (I don't think anybody should have/
characters in their field names but you never know, people do crazy things).