I created a function to memoize context values and return them as render props in React to avoid re-rendering when values change that I do not care about. (which works so thats great!) However, I am having trouble typing it.
The goal is to have the children function have the same type as the return type of accessor.
The part I am having a lot of trouble with is typing this part:
type ContextType = {
state:{ a:number; b:number; }
otherStuff:{ a:string; b:string }
}
export const ContextAccessor: <
T extends {
accessor: (ctx: ContextType) => ReturnType<T["accessor"]>;
children: (ctx: ReturnType<T["accessor"]>) => JSX.Element;
}
>(
args: T
) => JSX.Element = ({ children, accessor }): JSX.Element =>
My accessor function is being typed as accessor: (ctx: ContextType) => any
which then makes the function children
have an input parameter type of any as well.
I think it is because ReturnType<T["accessor"]>
does not actually work
The full code (without memoization) is below:
type ContextType = {
state:{ a:number; b:number; }
otherStuff:{ a:string; b:string }
}
export const ContextAccessor: <
T extends {
accessor: (ctx: ContextType) => ReturnType<T["accessor"]>;
children: (ctx: ReturnType<T["accessor"]>) => JSX.Element;
}
>(
args: T
) => JSX.Element = ({ children, accessor }): JSX.Element => {
const { state, modifiers, accessors } = useMyContext();
const accessed = useMemo(
() => accessor({ state, modifiers, accessors }),
[state, modifiers, accessors]
);
return children(accessed)
};
export const Test = () => {
return (
<ContextAccessor accessor={(ctx) => ctx.state.a}>
{(transfer) => (
<div>
<div>{JSON.stringify(transfer)}</div>
</div>
)}
</ContextAccessor>
);
};
Edits of more things I have tried
The below I believe doesn't work because ReturnType<Props<T>["accessor"]>
is typed as unknown and isn't generic.
type Props<T> = {
accessor: (context: ContextType) => unknown;
children: (ctx: ReturnType<Props<T>["accessor"]>) => JSX.Element;
};
export function StagingBuilderContext<T>(props: Props<T>): JSX.Element {
CodePudding user response:
You can make it work by adding an explicit type signature to the accessor
prop function argument: ctx
~> ctx: ContextType
.
export const Test = () => {
return (
<ContextAccessor accessor={(ctx: ContextType) => ctx.state.a}>
{(transfer) => ( // inferred type for transfer is number
<div>
<div>{JSON.stringify(transfer)}</div>
</div>
)}
</ContextAccessor>
);
};
With this assertion the accessor
return value is immediately available, rather than having to be inferred from its usage, which enables correct inference on children
. I'm not entirely certain about using ReturnType<T["accessor"]>
in the accessor
property, as this refers to itself, but it does appear to work correctly. An alternative would be to use a type parameter for the return type.