Skip to content

Add type definitions for recoil #44756

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

Merged
merged 13 commits into from
May 29, 2020
Merged

Conversation

csantos42
Copy link
Contributor

Please fill in this template.

  • Use a meaningful title for the pull request. Include the name of the package modified.
  • Test the change in your own code. (Compile and run.)
  • Add or edit tests to reflect the change. (Run with npm test.)
  • Follow the advice from the readme.
  • Avoid common mistakes.
  • Run npm run lint package-name (or tsc if no tslint.json is present).

Select one of these and delete the others:

If adding a new definition:

  • The package does not already provide its own types, or cannot have its .d.ts files generated via --declaration
  • If this is for an NPM package, match the name. If not, do not conflict with the name of an NPM package.
  • Create it with dts-gen --dt, not by basing it on an existing project.
  • Represents shape of module/library correctly
  • tslint.json should be present and it shouldn't have any additional or disabling of rules. Just content as { "extends": "dtslint/dt.json" }. If for reason the some rule need to be disabled, disable it for that line using // tslint:disable-next-line [ruleName] and not for whole package so that the need for disabling can be reviewed.
  • tsconfig.json should have noImplicitAny, noImplicitThis, strictNullChecks, and strictFunctionTypes set to true.

@typescript-bot typescript-bot added New Definition This PR creates a new definition package. Where is Travis? labels May 15, 2020
@typescript-bot
Copy link
Contributor

typescript-bot commented May 15, 2020

@csantos42 Thank you for submitting this PR! I see this is your first time submitting to DefinitelyTyped 👋 - keep an eye on this comment as I'll be updating it with information as things progress.

Code Reviews

This PR adds a new definition, so it needs to be reviewed by a DT maintainer before it can be merged.

Status

  • ✅ No merge conflicts
  • ✅ Continuous integration tests have passed
  • ✅ Only a DT maintainer can merge changes when there are new packages added

Once every item on this list is checked, I'll ask you for permission to merge and publish the changes.


Diagnostic Information: What the bot saw about this PR
{
  "type": "info",
  "now": "-",
  "pr_number": 44756,
  "author": "csantos42",
  "owners": [],
  "dangerLevel": "ScopedAndConfiguration",
  "headCommitAbbrOid": "f6ddc8d",
  "headCommitOid": "f6ddc8da25b2f47bf62aa132a87fd2ce24d82eb4",
  "mergeIsRequested": false,
  "stalenessInDays": 0,
  "lastCommitDate": "2020-05-29T18:05:09.000Z",
  "lastCommentDate": "2020-05-29T17:40:30.000Z",
  "reviewLink": "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/44756/files",
  "hasMergeConflict": false,
  "authorIsOwner": false,
  "isFirstContribution": true,
  "popularityLevel": "Well-liked by everyone",
  "anyPackageIsNew": true,
  "packages": [
    "recoil"
  ],
  "files": [
    {
      "filePath": "types/recoil/index.d.ts",
      "kind": "definition",
      "package": "recoil"
    },
    {
      "filePath": "types/recoil/recoil-tests.ts",
      "kind": "test",
      "package": "recoil"
    },
    {
      "filePath": "types/recoil/tsconfig.json",
      "kind": "package-meta",
      "package": "recoil"
    },
    {
      "filePath": "types/recoil/tslint.json",
      "kind": "package-meta",
      "package": "recoil"
    }
  ],
  "hasDismissedReview": false,
  "ciResult": "pass",
  "lastReviewDate": "2020-05-29T18:19:28.000Z",
  "reviewersWithStaleReviews": [
    {
      "reviewedAbbrOid": "22328be",
      "reviewer": "smmoosavi",
      "date": "2020-05-21T00:52:24Z"
    },
    {
      "reviewedAbbrOid": "53fcac4",
      "reviewer": "acutmore",
      "date": "2020-05-16T16:48:28Z"
    }
  ],
  "approvalFlags": 4,
  "isChangesRequested": false
}

@danger-public
Copy link

danger-public commented May 15, 2020

Inspecting the JavaScript source for this package found some properties that are not in the .d.ts files.
The check for missing properties isn't always right, so take this list as advice, not a requirement.

recoil (unpkg)

was missing the following properties:

  1. useSetUnvalidatedAtomValues_UNSTABLE
  2. useTransactionObservation_UNSTABLE
  3. useTransactionSubscription_UNSTABLE

Generated by 🚫 dangerJS against f6ddc8d

@typescript-bot
Copy link
Contributor

👋 Hi there! I’ve run some quick measurements against master and your PR. These metrics should help the humans reviewing this PR gauge whether it might negatively affect compile times or editor responsiveness for users who install these typings.

Let’s review the numbers, shall we?

These typings are for a package that doesn’t yet exist on master, so I don’t have anything to compare against yet! In the future, I’ll be able to compare PRs to recoil with its source on master.

Comparison details 📊
Batch compilation
Type count 16266
Assignability cache size 34408
Language service measurements
Samples taken 135
Identifiers in tests 135
getCompletionsAtPosition
    Mean duration (ms) 300.8
    Mean CV 9.5%
    Worst duration (ms) 401.6
    Worst identifier loadable
getQuickInfoAtPosition
    Mean duration (ms) 306.6
    Mean CV 9.2%
    Worst duration (ms) 378.2
    Worst identifier getValue
System information
Node version v12.16.3
CPU count 2
CPU speed 2.095 GHz
CPU model Intel(R) Xeon(R) Platinum 8171M CPU @ 2.60GHz
CPU Architecture x64
Memory 6.8 GiB
Platform linux
Release 4.15.0-1082-azure

@davidmccabe
Copy link

I'm not too familiar with TypeScript but I don't see any indicator of covariance for the read-only values. Is that part of this?

@Albert-Gao
Copy link
Contributor

@davidmccabe Do you mean to mark the argument of the setValue as readonly?

Sadly, the keyword readonly in TS can only be applied to a property of an object, does not apply to an argument of a function. and the issue is closed :(

microsoft/TypeScript#18497

@Dreamacro
Copy link

can export the type like RecoilState? Sometimes need to wrap useRecoilState

@aappddeevv
Copy link

aappddeevv commented May 16, 2020

I'm not sure the RecoilValue type is right. If RecoilValue can be used in useRecoilState then it should be readwrite. But the definition is RecoilValue = RecoilValueReadOnly|RecoilState and useRecoilState(RecoilValue). Did I read that correctly? I'm not sure I'm looking in the right place.

I think I read the wrong code..it does look like it is set correctly.

dustinsoftware added a commit to dustinsoftware/recoil-playground that referenced this pull request May 17, 2020
Referencing this PR and stealing over portions that make sense

DefinitelyTyped/DefinitelyTyped#44756
@typescript-bot typescript-bot added the Other Approved This PR was reviewed and signed-off by a community member. label May 20, 2020
Copy link

@smmoosavi smmoosavi left a comment

Choose a reason for hiding this comment

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

RecoilState should be both covariance and contravariance. because it is both readable and writable.

but right now any RecoilState can be assigned to any RecoilState without any error.

@typescript-bot typescript-bot added the Revision needed This PR needs code changes before it can be merged. label May 20, 2020
@typescript-bot
Copy link
Contributor

@csantos42 One or more reviewers has requested changes. Please address their comments. I'll be back once they sign off or you've pushed new commits or comments. If you disagree with the reviewer's comments, you can "dismiss" the review using GitHub's review UI. Thank you!

@csantos42
Copy link
Contributor Author

@csantos42
Copy link
Contributor Author

can export the type like RecoilState? Sometimes need to wrap useRecoilState

@Dreamacro good idea, I'm going to add all the user-facing types to the exports. Note we might be doing a lot of renaming soon

@csantos42
Copy link
Contributor Author

I'm not sure the RecoilValue type is right. If RecoilValue can be used in useRecoilState then it should be readwrite. But the definition is RecoilValue = RecoilValueReadOnly|RecoilState and useRecoilState(RecoilValue). Did I read that correctly? I'm not sure I'm looking in the right place.

I think I read the wrong code..it does look like it is set correctly.

@aappddeevv the type names are a little confusing at the moment, we'll work on improving that

@typescript-bot typescript-bot added The Travis CI build failed and removed Revision needed This PR needs code changes before it can be merged. labels May 26, 2020
@mathieucaroff
Copy link

I'm not sure when the pull request will be merged, but I wanted to use recoil's type definitions today, so I copy pasted them to a .d.ts file in my project. Here's what I ended up with:

declare module 'recoil' {
    // @include state.d.ts
    type NodeKey = string
    type AtomValues = Map<NodeKey, Loadable<any>>
    type ComponentCallback = (state: TreeState) => void
    type TreeState = Readonly<{
        isSnapshot: boolean
        transactionMetadata: object
        dirtyAtoms: Set<NodeKey>
        atomValues: AtomValues
        nonvalidatedAtoms: Map<NodeKey, unknown>
        nodeDeps: Map<NodeKey, Set<NodeKey>>
        nodeToNodeSubscriptions: Map<NodeKey, Set<NodeKey>>
        nodeToComponentSubscriptions: Map<
            NodeKey,
            Map<number, [string, ComponentCallback]>
        >
    }>

    // @include node.d.ts
    class DefaultValue {}

    // @include recoilRoot.d.ts
    import * as React from 'react'
    interface RecoilRootProps {
        initializeState?: (options: {
            set: <T>(recoilVal: RecoilState<T>, newVal: T) => void
            setUnvalidatedAtomValues: (atomMap: Map<string, unknown>) => void
        }) => void
    }
    const RecoilRoot: React.FC<RecoilRootProps>

    // @include atom.d.ts
    interface AtomOptions<T> {
        key: NodeKey
        default: RecoilValue<T> | Promise<T> | T
        dangerouslyAllowMutability?: boolean
    }
    /**
     * Creates an atom, which represents a piece of writeable state
     */
    function atom<T>(options: AtomOptions<T>): RecoilState<T>

    // @include selector.d.ts
    type GetRecoilValue = <T>(recoilVal: RecoilValue<T>) => T
    type SetRecoilState = <T>(
        recoilVal: RecoilState<T>,
        newVal: T | DefaultValue | ((prevValue: T) => T | DefaultValue),
    ) => void
    type ResetRecoilState = (recoilVal: RecoilState<any>) => void
    interface ReadOnlySelectorOptions<T> {
        key: string
        get: (opts: { get: GetRecoilValue }) => Promise<T> | RecoilValue<T> | T
        dangerouslyAllowMutability?: boolean
    }
    interface ReadWriteSelectorOptions<T> extends ReadOnlySelectorOptions<T> {
        set: (
            opts: {
                set: SetRecoilState
                get: GetRecoilValue
                reset: ResetRecoilState
            },
            newValue: T | DefaultValue,
        ) => void
    }
    function selector<T>(options: ReadWriteSelectorOptions<T>): RecoilState<T>
    function selector<T>(
        options: ReadOnlySelectorOptions<T>,
    ): RecoilValueReadOnly<T>

    // @include hooks.d.ts
    type SetterOrUpdater<T> = (valOrUpdater: ((currVal: T) => T) | T) => void
    type Resetter = () => void
    type CallbackInterface = Readonly<{
        getPromise: <T>(recoilVal: RecoilValue<T>) => Promise<T>
        getLoadable: <T>(recoilVal: RecoilValue<T>) => Loadable<T>
        set: <T>(
            recoilVal: RecoilState<T>,
            valOrUpdater: ((currVal: T) => T) | T,
        ) => void
        reset: (recoilVal: RecoilState<any>) => void
    }>
    /**
     * Returns the value of an atom or selector (readonly or writeable) and
     * subscribes the components to future updates of that state.
     */
    function useRecoilValue<T>(recoilValue: RecoilValue<T>): T
    /**
     * Returns a Loadable representing the status of the given Recoil state
     * and subscribes the component to future updates of that state. Useful
     * for working with async selectors.
     */
    function useRecoilValueLoadable<T>(recoilValue: RecoilValue<T>): Loadable<T>
    /**
     * Returns a tuple where the first element is the value of the recoil state
     * and the second is a setter to update that state. Subscribes component
     * to updates of the given state.
     */
    function useRecoilState<T>(
        recoilState: RecoilState<T>,
    ): [T, SetterOrUpdater<T>]
    /**
     * Returns a tuple where the first element is a Loadable and the second
     * element is a setter function to update the given state. Subscribes
     * component to updates of the given state.
     */
    function useRecoilStateLoadable<T>(
        recoilState: RecoilState<T>,
    ): [Loadable<T>, SetterOrUpdater<T>]
    /**
     * Returns a setter function for updating Recoil state. Does not subscribe
     * the component to the given state.
     */
    function useSetRecoilState<T>(
        recoilState: RecoilState<T>,
    ): SetterOrUpdater<T>
    /**
     * Returns a function that will reset the given state to its default value.
     */
    function useResetRecoilState(recoilState: RecoilState<any>): Resetter
    /**
     * Returns a function that will run the callback that was passed when
     * calling this hook. Useful for accessing Recoil state in response to
     * events.
     */
    function useRecoilCallback<Args extends ReadonlyArray<unknown>, Return>(
        fn: (interface: CallbackInterface, ...args: Args) => Return,
        deps?: ReadonlyArray<unknown>,
    ): (...args: Args) => Return

    // @include loadable.d.ts
    type ResolvedLoadablePromiseInfo<T> = Readonly<{
        value: T
        upstreamState__INTERNAL_DO_NOT_USE?: TreeState
    }>
    type LoadablePromise<T> = Promise<ResolvedLoadablePromiseInfo<T>>
    type Loadable<T> =
        | Readonly<{
              state: 'hasValue'
              contents: T
          }>
        | Readonly<{
              state: 'hasError'
              contents: Error
          }>
        | Readonly<{
              state: 'loading'
              contents: LoadablePromise<T>
          }>

    // @include recoilValue.d.ts
    class AbstractRecoilValue<T> {
        tag: 'Writeable'
        valTag: T
        key: NodeKey
        constructor(newKey: NodeKey)
    }
    class AbstractRecoilValueReadonly<T> {
        tag: 'Readonly'
        valTag: T
        key: NodeKey
        constructor(newKey: NodeKey)
    }
    class RecoilState<T> extends AbstractRecoilValue<T> {}
    class RecoilValueReadOnly<T> extends AbstractRecoilValueReadonly<T> {}
    type RecoilValue<T> = RecoilValueReadOnly<T> | RecoilState<T>
    function isRecoilValue(val: unknown): val is RecoilValue<any>
}

@typescript-bot
Copy link
Contributor

@smmoosavi, @acutmore Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review?

@csantos42 csantos42 requested a review from sheetalkamat May 27, 2020 02:03
@amcasey amcasey mentioned this pull request May 28, 2020
12 tasks
@kolodny
Copy link
Contributor

kolodny commented May 28, 2020

I've done some work independently, not realizing this PR existed. I have a couple of the tricky cases worked out. The types are pretty well tested and are quite robust. Feel free to incorporate anything from that branch to this PR.

kolodny@7bae698#diff-536a520cf5eea35920533711cf917823

@typescript-bot
Copy link
Contributor

@amcasey, @smmoosavi, @acutmore Thank you for reviewing this PR! The author has pushed new commits since your last review. Could you take another look and submit a fresh review?

@amcasey
Copy link
Contributor

amcasey commented May 28, 2020

@csantos42 Thanks! I think that addresses @sheetalkamat's concern. She knows more about modules resolution than I do and may be able to suggest a way to get back your tidy multi-file structure, but it seems like there's enough excitement around these types to handle that in a separate PR.

Did you have a chance to look at @kolodny and pull out possible improvements?

@smmoosavi @Gregoor @acutmore @renanmav Remaining concerns?

@csantos42
Copy link
Contributor Author

@amcasey yes, we should be good to go on our end :)

@smmoosavi
Copy link

smmoosavi commented May 29, 2020

Remaining concerns?

@amcasey the issue with RecoilValueReadOnly fixed. but RecoilState has a problem yet. contravariance is not checked in writable states

problem

const stringAtom: RecoilState<string> = {} as any;
const stringOrNumberAtom: RecoilState<string|number> = stringAtom // no error. it should detect error, it should check contravariance. because RecoilState is writable

const [value, setValue] = useRecoilState<string|number>(stringOrNumberAtom)
setValue(5) // no error, ok, SetterOrUpdater<string|number> should accept number

const value2 = useRecoilValue(stringAtom) // expect value2 should be string, but it is 5

solution

we can add another key to writable states to check contravariance.
see solution in playground playground
there is other solution with one extra key: solution2

type NodeKey = string
declare type CB<T> = (t: T) => void;

export class AbstractRecoilValue<T> {
    ' covariance-value': [T];
    ' contravariance-value': [CB<T>];
    key: NodeKey;
    constructor(newKey: NodeKey);
}

export class AbstractRecoilValueReadonly<T> {
    ' covariance-value': [T];
    key: NodeKey;
    constructor(newKey: NodeKey);
}

also tag and valTag doesn't exist in js, it's better to have unintuitive names started with space, and with this new suggested types we can remove tag key.

@csantos42
Copy link
Contributor Author

thanks @smmoosavi ! Clever-- I didn't think of using a function type to ensure contravariance, I'll make that change now :)

Copy link
Contributor

@amcasey amcasey left a comment

Choose a reason for hiding this comment

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

I think @smmoosavi's concerns have been addressed and there's tremendous interest in this PR, so I'm going to preemptively merge it. @csantos42, please be responsive to subsequent feedback if it turns out I've merged too soon.

@typescript-bot typescript-bot added Maintainer Approved Self Merge This PR can now be self-merged by the PR author or an owner labels May 29, 2020
@amcasey amcasey merged commit 596b09f into DefinitelyTyped:master May 29, 2020
@csantos42
Copy link
Contributor Author

Thanks a lot @amcasey! And thanks to everyone that reviewed/contributed/commented on this PR! I'll be sure to monitor any subsequent feedback promptly

@itsMapleLeaf
Copy link
Contributor

Recoil 0.0.8 is published on npm, but the types are still missing a few things, like atomFamily and selectorFamily. I wouldn't mind adding those myself, I guess checking to see if someone's already working on it

@csantos42 csantos42 deleted the recoil-types branch May 31, 2020 21:47
@csantos42
Copy link
Contributor Author

hi @kingdaro, I'm working on this now, should be done some time tonight. Thanks!

jjballano-qatium pushed a commit to jjballano-qatium/DefinitelyTyped that referenced this pull request Jun 16, 2020
* Initial types

* TypeScript types for Recoil

* fix lint errors

* fix tests

* Remove unused types

* fix prettier error

* Improve types in response to feedback

* Remove loadable accessors

* Fix broken tests

* Fix reference to React types

* Fix lint error

* Consolidate types into one file to match src structure

* fix contravariance of writeable state:
ngbrown pushed a commit to ngbrown-forks/DefinitelyTyped that referenced this pull request Jul 11, 2020
* Initial types

* TypeScript types for Recoil

* fix lint errors

* fix tests

* Remove unused types

* fix prettier error

* Improve types in response to feedback

* Remove loadable accessors

* Fix broken tests

* Fix reference to React types

* Fix lint error

* Consolidate types into one file to match src structure

* fix contravariance of writeable state:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Maintainer Approved New Definition This PR creates a new definition package. Self Merge This PR can now be self-merged by the PR author or an owner
Projects
None yet
Development

Successfully merging this pull request may close these issues.