Skip to content
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

Add support for nested ZodEffects #1382

Merged
merged 3 commits into from
Nov 18, 2024

Conversation

sassanh
Copy link
Contributor

@sassanh sassanh commented Nov 4, 2024

Relevant: #1319

This extends what was done in #1319 so that it works when multiple refinements are added, for example:

const schema = z
  .object({
    username: z.string().min(4),
    password: z.string().min(8),
    confirmPassword: z.string().min(8),
  })
  .refine((form) => form.password === form.confirmPassword, {
    message: "Passwords don't match",
    path: ["confirmPassword"],
  })
  .refine((form) => form.password !== form.username, {
    message: "Password is the same as the username",
    path: ["password"],
  });

@piotrpospiech can you kindly take a look?
Do you think we can have it in version 4? It doesn't look like a new feature.

@github-actions github-actions bot added Area: Bridge Affects some of the bridge packages Bridge: Zod Affects the uniforms-bridge-zod package labels Nov 4, 2024
@piotrpospiech
Copy link
Collaborator

Hi @sassanh, thanks for the contribution! It seems we can add it to the upcoming 4 or 4.x version. I will come back with detailed answer and review next week.

@piotrpospiech piotrpospiech self-assigned this Nov 4, 2024
@sassanh sassanh force-pushed the nested-zod-effects branch from 89051a9 to 71b1cd8 Compare November 5, 2024 09:51
@sassanh sassanh force-pushed the nested-zod-effects branch from 71b1cd8 to 873a776 Compare November 5, 2024 10:56
Copy link
Collaborator

@piotrpospiech piotrpospiech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sassanh, great job with this pull request. Thanks for your input and adding new tests for the chained refine and superRefine functions.

Once those minor adjustments are made, we’ll be glad to proceed with the merge.

packages/uniforms-bridge-zod/__tests__/ZodBridge.ts Outdated Show resolved Hide resolved
packages/uniforms-bridge-zod/__tests__/ZodBridge.ts Outdated Show resolved Hide resolved
export default class ZodBridge<T extends ZodRawShape> extends Bridge {
schema: ZodObject<T> | ZodEffects<ZodObject<T>>;
schema: ZodObject<T> | NestedZodEffect<ZodObject<T>>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
schema: ZodObject<T> | NestedZodEffect<ZodObject<T>>;
schema: ZodObject<T> | ZodEffects<ZodSchema>

We decided we don't want to create a special type with depth limit to handle this. ZodEffects<ZodSchema> will allow nested ZodEffects type. It will be easier to understand, maintain and we don't want to hardcode specific depth limit.

The downside is that it ZodSchema allows any zod type. This is something that we need to accept.

Wrong type is still checked using invariant function in the runtime.

@sassanh
Copy link
Contributor Author

sassanh commented Nov 15, 2024

Hi @piotrpospiech, thanks for taking the time to review this!

I've made the changes you suggested in the comments. However, regarding your decision to avoid introducing a new type, I wanted to clarify that it does have a trade-off. The error objects returned by bridge.getValidator()(...) will be of type any.

Personally, I use Zod because it perfectly aligns with type hints. If I weren't using Zod, I'd use Yup, which is already supported by many libraries.

@piotrpospiech
Copy link
Collaborator

Hi @piotrpospiech, thanks for taking the time to review this!

I've made the changes you suggested in the comments. However, regarding your decision to avoid introducing a new type, I wanted to clarify that it does have a trade-off. The error objects returned by bridge.getValidator()(...) will be of type any.

Personally, I use Zod because it perfectly aligns with type hints. If I weren't using Zod, I'd use Yup, which is already supported by many libraries.

Good point. I missed that. To overcome this issue, I think we need to change the way we handle generics.

// packages/uniforms-bridge-zod/src/ZodBridge.ts

export default class ZodBridge<T> extends Bridge {
  schema: ZodType<T>;
// Example usage of ZodBridge

const schema = z.object({
  text: z.string(),
});
type SchemaType = z.infer<typeof schema>;

const bridge = new ZodBridge<SchemaType>({ schema });
const validator = bridge.getValidator();

Now, type of validator is:

const validator: (model: UnknownObject) => z.ZodError<{
    text: string;
}> | null

This implementation does come with a trade-off: when creating a ZodBridge object, we need to specify the schema type if we want a typed validator. However, this approach ensures the best type safety and is a common solution used in libraries that leverage zod for validation.

@sassanh
Copy link
Contributor Author

sassanh commented Nov 18, 2024

Thanks!

I decided to skip using a form helper library for the project I'm working on.

I'd be more than happy to work with you to merge this pull request, but I might not be able to get to it right away. Feel free to go ahead and commit changes to this branch.

@piotrpospiech piotrpospiech added this to the v4.0 milestone Nov 18, 2024
@piotrpospiech piotrpospiech merged commit ba543d1 into vazco:master Nov 18, 2024
1 of 4 checks passed
@piotrpospiech
Copy link
Collaborator

@sassanh, I added changes I described above and published new version (4.0.0-beta.4).

We're sorry to hear that you've chosen not to use our library. If you have any suggestions for improving the uniforms or the Zod bridge, or if there are any missing features you'd like to see, we'd greatly appreciate your feedback.

Thank you once again for your contribution and for helping to improve zod bridge.

@sassanh
Copy link
Contributor Author

sassanh commented Nov 19, 2024

We're sorry to hear that you've chosen not to use our library. If you have any suggestions for improving the uniforms or the Zod bridge, or if there are any missing features you'd like to see, we'd greatly appreciate your feedback.

Unfortunately, I can't recall the specific details. I tried using a few libraries, but none of them had all the features I needed. I can't remember which library lacked which feature.

Perhaps the Zod support wasn't as robust as I had hoped. Additionally, I need to implement this feature, and I'm not sure if your API's autosave functionality can handle it.

Imagine that {a: 1} is the initial state of a form. If the user enters 2 in the field named a, the form should report itself as dirty. This way, I can prevent the user from closing the window. What I needed was to inform the form library that I want the initial state to be changed to {a: 2} at some point (since it somehow gets synced with the server in the background) and I expect the library to not report it as dirty anymore. I remember it was not possible with react-hook-form in a clean way, not sure about uniforms.

Do you have a concept of dirty/edited/has-unsaved-changes in your API?

I also believe that Uniforms mostly works well with Antd and not so well with Material-UI. For instance, I expect to see field errors in <FormHelperText> for each field automatically like how react-form-hook does it.

Apologies for being vague. I would love to try it again and provide you with a detailed feedback, but unfortunately, I'm out of time now. I hope this feedback is helpful.

@piotrpospiech
Copy link
Collaborator

Do you have a concept of dirty/edited/has-unsaved-changes in your API?

You can check Context data. It is part of our BaseForm (source code), but you will need to pass it outside form. The uniforms package is primarily designed for generating forms directly from schemas, utilizing form components to manage state. This approach differs from libraries like React Hook Form, which take a more hands-on, code-driven approach to form management.

The Uniforms package is primarily designed to handle complex and dynamic forms, making it easier to design them using schemas. A key aspect of this approach is the use of serializable schemas like JSON Schema. For additional flexibility and everyday use cases, we use Zod schemas. Zod is not only simpler to start with but also offers powerful validation capabilities for more advanced scenarios.

I also believe that Uniforms mostly works well with Antd and not so well with Material-UI. For instance, I expect to see field errors in for each field automatically like how react-form-hook does it.

Probably, you are looking for showInlineError. I believe that documentation can be missleading there. We are working on rebuilding documentation for the upcoming v4 release.

To disable the ErrorsField (summary of errors), you can exclude it from the children prop by passing all other components except ErrorsField. Alternatively, you can use the errorsField prop to achieve the same result.

<AutoForm schema={bridge} showInlineError>
  <AutoFields />
  <SubmitField />
</AutoForm>
image

Thanks for a feedback. We will work on improving the parts of the documentation that were unclear to you in the upcoming v4 docs.

@sassanh sassanh deleted the nested-zod-effects branch November 20, 2024 04:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Bridge Affects some of the bridge packages Bridge: Zod Affects the uniforms-bridge-zod package
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

2 participants