Skip to content
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

future development contributions #3

Closed
ap0nia opened this issue Jun 15, 2024 · 1 comment
Closed

future development contributions #3

ap0nia opened this issue Jun 15, 2024 · 1 comment

Comments

@ap0nia
Copy link

ap0nia commented Jun 15, 2024

Summary

I'd like to contribute to the development of hook-form's framework agnostic core as well as framework specific adapters.

To accomplish this, I've implemented a framework-agnostic interpretation of react-hook-form with a react adapter that passes all of the tests in the main react-hook-form library.

My work and proof of concept can be found here: https://github.com/ap0nia/forms.js

There is no documentation other than the tests that I've ported over at this time, but I can definitely add anything as needed!

Project Information

I've co-located three packages within the repository.

  • common: An internal package with runtime utility functions that correlate to react-hook-form's utils folder.
  • core: The agnostic library that exposes two main class-based interfaces for controlling forms
    • form-control: A class that manages data for an entire form.
    • field-array: A class that accepts a control in order to abstract operations with field arrays on the form control.
  • react: React bindings for the core library.

Key Files

Status

I've passed all of the tests from react-hook-form and I think it's a solid proof of concept for the core library. I don't intend the react bindings to replace the existing library; I just created them as a baseline for future adapters and to enable me to test the core class functionality.

Next Steps

I'd like to know if there's anything I can do with this work. e.g. Should I open a PR to get things started? My code was written from scratch so there's no base to merge from 🤔

Implementation

Here is a mini-blog post about implementation details. It would be great to have a formal discussion about preparing the project for future development!

Classes

I used a class based approach since it was a lot simpler to model state mutations. One downside is that methods are not auto-bound to their instances, however this is actually a work around for this by using the arrow function syntax, so it's feasible to write the core API as a class, but retain the properties of an object.

Functions/Methods

I tried to use the original method names wherever possible, and I took liberty of adding/removing layers of abstraction to aid the (subjective) readability of the code. This can definitely be refactored as needed and I hope it doesn't detract from the legitimacy of the proof of concept.

Observable

I applied the general idea of writable stores, i.e. the same ones as Svelte, to represent every individual state value. It's possible to subscribe to each one individually. However, React and react-hook-form want to optimize number of render counts by ###batching### updates wherever possible, so I engineered my solution around a batchable observable that can subscribe to multiple stores and only update on certain events.

Batchable

The batchable requires an object that maps keys to observables. For example:

const myWritableMapping = {
  isDirty: new Writable(false),
  isValid: new Writable(false)
}

Here, the keys isDirty and isValid are mapped to an observable (writable). The batchable subscribes to both stores, and when either
of them change, the batchable will update itself and notify its own subscribers. i.e. A consumer will subscribe to the batchable, and the batchable will subscribe to all the stores.

Tracking

However, the batchable will only notify for updates with valid combinations of key and context.

  • Key a key just represents a key in the mapping, for instance "isDirty" and "isValid" are valid string keys to access the object mapping above.
  • Context a context is an accompanying value, usually a string or string array, that can fine-tune the update.

Practical Example of Tracking

const writables = {
  values: new Writable({})
}

const batchable = new Batchable(writables)

batchable.track('values', 'username')

batchable.subscribe(console.log)

// NO console.log, since this didn't include the correct context, i.e. "username"
writables.values.set({})

// YES console.log, this was a change specifically triggered by "username"
writables.values.set({}, 'username')

You want to subscribe to updates to values but only when username triggered the change.

Batching
Finally, to quickly summarize batching, the combinations of changes triggered, e.g. ["values", "username"] might indicate that the store at the "values" key was changed with the "username" context, are stored in a buffer. When the batchable is flushed, all the combinations are parsed, and if any of them are being tracked, then a notification is triggered. Otherwise the batchable is already up to date, but does not send any notifications (and thus doesn't trigger any re-renders).

batchable vs. react-hook-form subjects

React-hook-form manually performs a comparison of the current formState and the next formState, evaluates which keys have changed, etc. whenever deciding whether to render. Although there's definitely a lot of room for refinement, I created the batchable + observable pattern as an alternative where you could 1) cork the batchable, 2) perform any updates needed on any number of observables, 3) flush the batchable and send a single notification if needed.

And this system would hopefully be sophisticated enough where if you followed its documented procedures and understood its behavior well, you wouldn't need to do manual confirmations for each render.

Also because all updates are proxied through this single location, it may be possible to implement additional dev tools that visualize the flow of data in/out of the batchable interface, in addition to building more robust flows like flux on top of it if needed.

@ap0nia
Copy link
Author

ap0nia commented Jul 5, 2024

Will close this in favor of following #4 for future updates.

@ap0nia ap0nia closed this as completed Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant