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

[HOLD for payment 2024-02-26] [TS Migration] Improve form validation return type #35318

Closed
roryabraham opened this issue Jan 29, 2024 · 22 comments
Assignees
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.

Comments

@roryabraham
Copy link
Contributor

Coming from #32992 (review)...

Problem

Currently, the object returned by validate is just a Record<string, string>, but we know that the return type should be a map of formInputID: translationKey, so we're missing an opportunity to be precise and expressive with our type definitions. Some more specific problems with this implementation are:

  • It's easy to put whatever string you want in the return type (for example, I was mistakenly using translate(translationKey), when all I needed was translationKey).
  • If you use a constant for a form inputID (which is a good practice), then you'll get a type error referencing it in the return type of validate and have to cast it to a normal string (with as string).

Solution

Implement the fixes @blazejkustra laid out here.

@blazejkustra
Copy link
Contributor

I would love to work on this one 😄

@roryabraham
Copy link
Contributor Author

roryabraham commented Jan 31, 2024

This is kind of blocking my work on https://github.com/Expensify/App/pull/34925/files, and despite lots of effort trying to make progress on it, I'm unfortunately kind of stuck.

I've tried something like this:

const FORM_IDS = {
    ADD_DEBIT_CARD: 'AddDebitCardForm',
} as const;

// TODO: how can we enforce that all the values of FORM_IDS are included as keys in this,
// without losing type information
const INPUT_IDS = {
    [FORM_IDS.ADD_DEBIT_CARD]: {
        SETUP_COMPLETE: 'setupComplete',
    },
} as const;

I've tried this:

const INPUT_IDS: Record<ValueOf<typeof FORM_IDS>, Record<string, string>> = {
    [FORM_IDS.ADD_DEBIT_CARD]: {
        SETUP_COMPLETE: 'setupComplete',
    },
} as const;

But that doesn't enforce that all values from FORM_IDS are included as keys in INPUT_IDS. It also makes us lose type information instead of SETUP_COMPLETE: 'setupComplete', we just have Record<string, string>.

Maybe there's a type-fest utility that can help here? Exact, perhaps? We essentially want a mapped type as a constant, but I wasn't able to figure out how to accomplish that.

Then, I got stuck on the next part as well. I was attempting to create a type from FORM_IDS and INPUT_IDS such that:

  • every form has a base set of properties
  • every form defines types for all the inputIDs it defined in INPUT_IDS

I tried this:

type FormData = {
    [FORM_IDS.ADD_DEBIT_CARD]: {
        // TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type
        [INPUT_IDS[FORM_IDS.ADD_DEBIT_CARD].SETUP_COMPLETE]: boolean,
    },
};

This doesn't work, and again we'd have a similar struggle ensuring that all forms and inputIDs are represented.

@roryabraham
Copy link
Contributor Author

I also ran into some trouble adding RadioButtons as a ValidInput, specifically around the onPress prop. Currently onPress is defined to receive a single GestureResponderEvent argument, but in reality it can receive different treatment depending on the input that it's used with.

I think the root cause for this problem, essentially, is that BaseInputProps is incorrect, in that there's nothing to ensure that all of the inputs accept those props as they're defined.

@roryabraham
Copy link
Contributor Author

roryabraham commented Jan 31, 2024

Another thing I found a bit confusing was that FormValueType is defined loosely as string | boolean | Date, but I'd think that the value type would depend on which form is being referenced.

For example, if we're talking about the FormValueType for AddDebitCardForm, it would basically be: ValueOf<AddDebitCardForm>

@roryabraham
Copy link
Contributor Author

Kind of dumping a lot of ideas here, but that's partially because I'm having difficulty wrapping my head around a lot of this, and just trying to make sense of it.

@blazejkustra
Copy link
Contributor

I think the root cause for this problem, essentially, is that BaseInputProps is incorrect, in that there's nothing to ensure that all of the inputs accept those props as they're defined.

Yes BaseInputProps is the root issue, I'll fix it in a follow-up.

Another thing I found a bit confusing was that FormValueType is defined loosely as string | boolean | Date, but I'd think that the value type would depend on which form is being referenced.

I'll try to fix it 👍

@blazejkustra
Copy link
Contributor

But that doesn't enforce that all values from FORM_IDS are included as keys in INPUT_IDS. It also makes us lose type information instead of SETUP_COMPLETE: 'setupComplete', we just have Record<string, string>.

Maybe there's a type-fest utility that can help here? Exact, perhaps? We essentially want a mapped type as a constant, but I wasn't able to figure out how to accomplish that.

When I create INPUT_IDS mapping I'll make sure it requires to provide all FORM_IDS as keys, it may be complicated but worth trying for sure 👍

@roryabraham
Copy link
Contributor Author

roryabraham commented Jan 31, 2024

Another NAB improvement that would be nice to see is if we could eliminate the need to define every form key and type twice – once for the form and again for the form draft. This is another thing I couldn't figure out how to do without losing type information. I tried something like this:

import {Replace} from 'type-fest';

const formIDs = {
    ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
    WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm',
    WORKSPACE_RATE_AND_UNIT_FORM: 'workspaceRateAndUnitForm',
} as const;

type FormIDs = typeof formIDs;

type DraftFormIDs = {
    [Key in keyof FormIDs | `${keyof FormIDs}_DRAFT`]: Key extends keyof FormIDs ? FormIDs[Key] : `${FormIDs[Replace<Key, '_DRAFT', ''>]}Draft`;
};

const formIDsWithDrafts: DraftFormIDs = Object.entries(formIDs).reduce((acc, [key, value]) => {
    acc[key] = value;
    acc[`${key}_DRAFT`] = `${value}Draft`;
}, {});

But that doesn't quite work. I did find this complicated TS code that provides a better-typed version of Object.entries, but it still didn't make it clear how to accomplish what I was trying to.

@roryabraham
Copy link
Contributor Author

Reviewed @blazejkustra's WIP PR

@melvin-bot melvin-bot bot added Reviewing Has a PR in review Weekly KSv2 and removed Overdue Daily KSv2 labels Feb 6, 2024
@blazejkustra
Copy link
Contributor

PR is ready for C+ review, I tested in on all platforms and finished reviewer checklist. cc @roryabraham @fabioh8010

@abdulrahuman5196
Copy link
Contributor

@roryabraham Could you kindly assign me as C+ here?

@melvin-bot melvin-bot bot added Weekly KSv2 Awaiting Payment Auto-added when associated PR is deployed to production and removed Weekly KSv2 labels Feb 19, 2024
@melvin-bot melvin-bot bot changed the title [TS Migration] Improve form validation return type [HOLD for payment 2024-02-26] [TS Migration] Improve form validation return type Feb 19, 2024
@melvin-bot melvin-bot bot removed the Reviewing Has a PR in review label Feb 19, 2024
Copy link

melvin-bot bot commented Feb 19, 2024

Reviewing label has been removed, please complete the "BugZero Checklist".

Copy link

melvin-bot bot commented Feb 19, 2024

The solution for this issue has been 🚀 deployed to production 🚀 in version 1.4.42-5 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

If no regressions arise, payment will be issued on 2024-02-26. 🎊

For reference, here are some details about the assignees on this issue:

@Julesssss
Copy link
Contributor

Assigning @abdulrahuman5196 based on this comment

@roryabraham roryabraham added the NewFeature Something to build that is a new item. label Feb 23, 2024
Copy link

melvin-bot bot commented Feb 23, 2024

@roryabraham
Copy link
Contributor Author

flagging as new feature to handle C+ payment

@MitchExpensify
Copy link
Contributor

Reminder set to pay tomorrow

@abdulrahuman5196
Copy link
Contributor

No BZ checklist since we improved the TS representation of Form component

cc: @MitchExpensify

@melvin-bot melvin-bot bot added Daily KSv2 and removed Weekly KSv2 labels Feb 26, 2024
Copy link

melvin-bot bot commented Feb 26, 2024

Payment Summary

Upwork Job

BugZero Checklist (@MitchExpensify)

  • I have verified the correct assignees and roles are listed above and updated the neccesary manual offers
  • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants//hired)
  • I have paid out the Upwork contracts or cancelled the ones that are incorrect
  • I have verified the payment summary above is correct

@MitchExpensify
Copy link
Contributor

@abdulrahuman5196
Copy link
Contributor

@MitchExpensify accepted the offer

@MitchExpensify
Copy link
Contributor

paid, contract ended!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.
Projects
No open projects
Development

No branches or pull requests

5 participants