最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Typescript: Partial Inference on function argument not working - Stack Overflow

programmeradmin1浏览0评论

I am creating a state management solution for building a form wizard (multi-step form) in react and typescript. I am super close in getting a fully type-safe configuration helper. Almost all of the type inference is working except for the getNextStep property. I've included a code sandbox link so you can inspect the types. Hovering over the 'data' argument on line 44, the type is 'any' where it should be the inferred zod type of the schema property on the same object. For example, in Step1, the type for data in getNextStep should resolve to { userId: string }. The types are setup this way so that the component props can also be inferred based on which step is being accessed. An example can be seen in the Test component at the bottom. I need to be able to infer the correct component props so I can render the component for the step in a type-safe way. Any help is appreciated here.

CodeSanbox Link

import { Context, ComponentType, createContext, useContext } from "react";
import { z } from "zod";

// Step type with explicit generic for component props
type Step<
  TKey = string,
  TSchema extends z.ZodType = z.ZodType,
  TProps = never
> = {
  schema: TSchema;
  component: ComponentType<TProps>;
  getNextStep?: (data: z.infer<TSchema>) => TKey;
};

type Config<TKey extends string = string> = {
  [K in TKey]: Step<Exclude<TKey, K>>;
};

type InferData<TConfig> = {
  [K in keyof TConfig]: TConfig[K] extends { schema?: z.ZodType }
    ? z.infer<NonNullable<TConfig[K]["schema"]>>
    : unknown;
};

function createConfig<TConfig extends Config<keyof TConfig & string>>(
  config: TConfig
) {
  const Context = createContext<TConfig | null>(null);

  const useConfig = createUseConfig(Context);
  const useConfigStep = createUseConfigStep(Context);

  return Object.freeze({
    useConfig,
    useConfigStep,
    config,
  });
}

const { useConfig, useConfigStep, config } = createConfig({
  Step1: {
    component: ({ Id }: { Id: string }) => null,
    schema: z.object({ userId: z.string() }),
    getNextStep: (data) => "Step2",
  },
  Step2: {
    component: ({ userId }: { userId: string }) => null,
    schema: z.unknown(),
  },
});

type Data = InferData<typeof config>;

function createUseConfig<TConfig extends Config<keyof TConfig & string>>(
  Context: Context<TConfig | null>
) {
  return function useConfig() {
    const store = useContext(Context);

    if (!store) {
      throw new Error(
        "useFormWizardStore must be used within a FormWizardProvider"
      );
    }

    return store;
  };
}

function createUseConfigStep<TConfig extends Config<keyof TConfig & string>>(
  Context: Context<TConfig | null>
) {
  return function useConfigStep<TKey extends keyof TConfig & string>(
    step: TKey
  ) {
    const store = useContext(Context);

    if (!store) {
      throw new Error(
        "useFormWizardStore must be used within a FormWizardProvider"
      );
    }

    return store[step];
  };
}

function Test() {
  const { component: Component } = useConfigStep("Step1");
  return <Component Id="" />;
}
发布评论

评论列表(0)

  1. 暂无评论