T
TanStack•13mo ago
modern-teal

Struggling to make use of `Updater<T>`

So, I'm trying to replicate React Final Form's parse and format properties, but I've hit a snag with Updater<T>. Trying to type-narrow handleChange's callback param so I can apply a parse function to it, gives me the following error
This expression is not callable.
Not all constituents of type 'UpdaterFn<T, T> | (T & Function)' are callable.
Type 'T & Function' has no call signatures.ts(2349)
This expression is not callable.
Not all constituents of type 'UpdaterFn<T, T> | (T & Function)' are callable.
Type 'T & Function' has no call signatures.ts(2349)
I'm probably going about this all wrong, so if there's a much simpler built-in way of doing this that I haven't figured out yet, I'd appreciate the help 😅 For reference
// The use of `any` here is fine, because tanstack form handles typechecking for
// everything else. Here, we're only interested in the type of the field's value
// for the purpose of wrapping it for form components
export type TanstackField<T> = FieldApi<any, any, any, any, T>;

// No type transforms here... yet
type WrapFieldData<T> = {
parse?: (v: T) => T;
format?: (v: T) => T;
};
export const wrapField = <T>(
field: TanstackField<T>,
enhancers: WrapFieldData<T> = {}
): TanstackField<T> => {
const parse = enhancers.parse ? enhancers.parse : identity;
const format = enhancers.format ? enhancers.format : identity;

return {
...field,
handleChange: v => {
const val = typeof v === "function" ? v() : v;
return field.handleChange(parse(val));
},
state: { ...field.state, value: format(field.state.value) },
// Typescript complains about these 2 missing methods, despite the fact
// that they'll get splatted there at runtime
runValidator: field.runValidator,
setErrorMap: field.setErrorMap
};
};
// The use of `any` here is fine, because tanstack form handles typechecking for
// everything else. Here, we're only interested in the type of the field's value
// for the purpose of wrapping it for form components
export type TanstackField<T> = FieldApi<any, any, any, any, T>;

// No type transforms here... yet
type WrapFieldData<T> = {
parse?: (v: T) => T;
format?: (v: T) => T;
};
export const wrapField = <T>(
field: TanstackField<T>,
enhancers: WrapFieldData<T> = {}
): TanstackField<T> => {
const parse = enhancers.parse ? enhancers.parse : identity;
const format = enhancers.format ? enhancers.format : identity;

return {
...field,
handleChange: v => {
const val = typeof v === "function" ? v() : v;
return field.handleChange(parse(val));
},
state: { ...field.state, value: format(field.state.value) },
// Typescript complains about these 2 missing methods, despite the fact
// that they'll get splatted there at runtime
runValidator: field.runValidator,
setErrorMap: field.setErrorMap
};
};
1 Reply
modern-teal
modern-tealOP•13mo ago
A solution that I've decided to settle with in the meantime, is to manually cast v to UpdaterFn, but I'd rather have a solution that didn't use casting. I'd rather not lose type if I can help it.
handleChange: v => {
// Need to manually cast v to UpdaterFn because typescript tries to
// distribute the Function type across the union for some strange reason
const val =
typeof v === "function"
? (v as UpdaterFn<T>)(field.state.value)
: v;

return field.handleChange(parse(val));
},
handleChange: v => {
// Need to manually cast v to UpdaterFn because typescript tries to
// distribute the Function type across the union for some strange reason
const val =
typeof v === "function"
? (v as UpdaterFn<T>)(field.state.value)
: v;

return field.handleChange(parse(val));
},

Did you find this page helpful?