-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
v3 #2846
v3 #2846
Conversation
* Moved default number parse logic from onChange to parse * tests: onChange is no longer equal to handleChange * Remove parse and format from input props
🦋 Changeset detectedLatest commit: 1c36c1c The changes in this PR will be included in the next version bump. This PR includes no changesetsWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/formium/formik/160by3vfz |
Size Change: +3.55 kB (8%) 🔍 Total Size: 43.3 kB
ℹ️ View Unchanged
|
Version Packages (next)
@jaredpalmer I think the lack of what I will call "dynamic slicing" is going to be a problem as the Formik API continues to evolve. The basic issue is this: You need to know what hooks need to be called in order to build a component with these useContextSelector-based hooks. You must be able to write a component which selects specific bits of Formik's state. You can't subscribe to a "random cross-section". And you cannot change those hooks based on dynamic props. If you need a random cross-section, you'll have to subscribe to all of Formik's state and select it yourself, leading back to where we are today. A specific API which I'm thinking would be impossible with this is one I've been thinking of for a while: <Field
type="number"
name="width"
include={fields => fields.height}
validate={(value, height) => value * height.value > 100
? "Max of 100 square feet."
: ""
}
/> I believe my subscription based approach described here would enable that API in a performant way. |
Why wouldn't useContextSelector be able to handle this (assuming you memoized the selector)? |
Meanwhile, in my PR, each Field only uses one subscription, as well as a normal Context subscription to access the API, which almost never updates due to stable references (once again, we can improve). Note there are 3 total subscribers, with 1 dispatch effect each. And if multiple fields were all listening to the same selector + args + comparer combo, for example, In the 500 input performance test, this PR has 7502 listeners to Context, while @formik/reducer-refs has 500. I feel a performance difference when holding down a key in this test, on my PC. In this pr, while holding down a key my PC will never render a new value until I let go. On @formik/reducer-refs, it will print out about every 6 or 7th key regularly. That's about as scientific as I've bothered to get lol. |
For reference: dai-shi/use-context-selector#19 (comment) |
@dai-shi custom equality is only one difference between my implementation and useContextSelector.
not using the context (except to access the API) means you can use the internal values wherever you have access to the API, with my PR you can do something like: const formik = useFormik(...props);
// you can subscribe to the "mutable source" without a context, much like you will be able to after useMutableSource lands
const formikSlice = useFormikStateInternal(formik, selectIsSubmitting, Object.is); Or you could even pass a ref to formik and subscribe to it from any context you want (well, in theory, haven't tried).
this particular latest value guarantee is necessary due to the fact that in many cases form state is not a function of React effects, but a function of user input which does not wait for effects to complete. onSubmit={async (values, api) => {
const result = await checkApiThing();
// make sure these form values aren't stale
const stillRelevant = isEqual(values, api.getValues());
if (result.success && stillRelevant) {
doImportantStuff(values);
}
}} |
@johnrom sorry, i'm not familiar with context of the discussion here in formik.
|
@dai-shi thanks for your response. I'm familiar with the power of useContextSelector in optimizing context and suggested its use for Formik many months ago now. But after a lot of thinking, I have come to the conclusion that Formik should avoid using Context except to pass down its API and instead provide state access via direct and internally controlled subscriptions. There are plenty of packages that do this out of the box, like useSubscription, redux, etc, but the implementation in my PR is specifically crafted to handle the large number of similar subscriptions and updates that Formik generates by having dozens or more components subscribing to reusable cross-sections of state. |
Got it. Yeah, form store makes sense if that's the requirement. (I mean I know both needs. like, some of my libs are for react state and some are for module state.) It's just a bit unfortunate.. |
@dai-shi would you be interested in reviewing my subscription implementation? I wrote the subscription part completely independent of Formik's general API and it is suitable to be broken into a separate package. There is overlap between this and useContextSelector, and I wonder if they can work together as a method to subscribe to Context. The two files related to subscriptions are here: |
Is there a material performance difference between sharing a subscription and creating new ones with useContextSelect for forms with < 100 fields? Does this actually matter to end users? |
I didn't set out to optimize the subscription mechanism per se when I created my PR. I set out to include a subscription API that can allow us to:
I couldn't find anything that fit the bill (including useMutableSource), so in order to proof that, I had to write a new subscription mechanism. Could definitely ship with useContextSelector level of performance (as long as common subscriptions don't use deep equality), but performance is not my first goal here. |
It's more complicated than expected. Maybe because it's tree structure with subscriptions to avoid duplicated invocation of selectors? |
This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days |
The API appears to be missing |
The various Take useValues for example: export function useValues<Values>() {
const state = useFormikContextSelector<
Values,
FormikContextType<Values>['values']
>(ctx => ctx.values);
const update = useFormikContextSelector<
Values,
FormikContextType<Values>['setValues']
>(ctx => ctx.setValues);
return [state, update];
} The type that TypeScript infers is |
This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days |
useIsDirty
,useIsSubmitting
, anduseIsValid
return booleansconst [values, setValues] = useValues()
)useSetXXXX
just return the setter (and have no perf overhead)Todo:
useFieldInitialValue
useFieldInitialTouched
useFieldInitialError
useInitialValues
useInitialErrors
useInitialTouched
useInitialStatus
useFieldTouched
useFieldValue
useFieldError
useSetFieldValue
useSetFieldTouched
useSetFieldError
useValues
useSetValue
useSetErrors
useErrors
useTouched
useSetTouched
useStatus
useSetStatus
useSubmitForm
useResetForm
useIsSubmitting
useIsValid
useIsDirty
useValidateForm
useValidateField
useActions
??? (bag of all methods)?validateForm
andvalidateField
with hooksForm
,ErrorMessage
, andField
Field
andFastField
render props