Home > other >  How do I get the return type of a function to be the input of another function in the same interface
How do I get the return type of a function to be the input of another function in the same interface

Time:03-22

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.

TypeScript playground

  • Related