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 type definitions for recoil #44756

Merged
merged 13 commits into from
May 29, 2020
29 changes: 29 additions & 0 deletions types/recoil/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Type definitions for recoil 0.0
// Project: https://github.com/facebookexperimental/recoil#readme
// Definitions by: Christian Santos <https://github.com/csantos42>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Minimum TypeScript Version: 3.7

// Nominal Classes
export { DefaultValue } from './lib/core/node';

// Components
export { RecoilRoot } from './lib/core/RecoilRoot';

// Recoil state
export { atom } from './lib/core/atom';
export { selector } from './lib/core/selector';

// Hooks
export {
useRecoilValue,
useRecoilValueLoadable,
useRecoilState,
useRecoilStateLoadable,
useSetRecoilState,
useResetRecoilState,
useRecoilCallback,
} from './lib/core/hooks';

// Other
export { isRecoilValue } from './lib/core/recoilValue';
11 changes: 11 additions & 0 deletions types/recoil/lib/core/RecoilRoot.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FC } from 'react';
csantos42 marked this conversation as resolved.
Show resolved Hide resolved
import { RecoilState } from './recoilValue';

export interface RecoilRootProps {
initializeState?: (options: {
set: <T>(recoilVal: RecoilState<T>, newVal: T) => void;
setUnvalidatedAtomValues: (atomMap: Map<string, unknown>) => void;
}) => void;
}

export const RecoilRoot: FC<RecoilRootProps>;
11 changes: 11 additions & 0 deletions types/recoil/lib/core/atom.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NodeKey } from './state';
import { RecoilValue, RecoilState } from './recoilValue';

export type AtomOptions<T> = Readonly<{
csantos42 marked this conversation as resolved.
Show resolved Hide resolved
key: NodeKey;
default: RecoilValue<T> | Promise<T> | T;
// persistence_UNSTABLE?: PersistenceSettings<T>,
dangerouslyAllowMutability?: boolean;
}>;

export function atom<T>(options: AtomOptions<T>): RecoilState<T>;
22 changes: 22 additions & 0 deletions types/recoil/lib/core/hooks.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RecoilValue, RecoilState } from './recoilValue';
import { Loadable } from './loadable';

export type SetterOrUpdater<T> = (valOrUpdater: ((currVal: T) => T) | T) => void;
export type Resetter = () => void;
export 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;
}>;

export function useRecoilValue<T>(recoilValue: RecoilValue<T>): T;
export function useRecoilValueLoadable<T>(recoilValue: RecoilValue<T>): Loadable<T>;
export function useRecoilState<T>(recoilState: RecoilState<T>): [T, SetterOrUpdater<T>];
export function useRecoilStateLoadable<T>(recoilState: RecoilState<T>): [Loadable<T>, SetterOrUpdater<T>];
export function useSetRecoilState<T>(recoilState: RecoilState<T>): SetterOrUpdater<T>;
export function useResetRecoilState(recoilState: RecoilState<any>): Resetter;
export function useRecoilCallback<Args extends ReadonlyArray<unknown>, Return>(
fn: (interface: CallbackInterface, ...args: Args) => Return,
deps?: ReadonlyArray<unknown>,
): (...args: Args) => Return;
37 changes: 37 additions & 0 deletions types/recoil/lib/core/loadable.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { TreeState } from './state';

export type ResolvedLoadablePromiseInfo<T> = Readonly<{
value: T;
upstreamState__INTERNAL_DO_NOT_USE?: TreeState;
}>;

export type LoadablePromise<T> = Promise<ResolvedLoadablePromiseInfo<T>>;

export type Accessors<T> = Readonly<{
// Attempt to get the value.
// If there's an error, throw an error. If it's still loading, throw a Promise
// This is useful for composing with React Suspense or in a Recoil Selector.
csantos42 marked this conversation as resolved.
Show resolved Hide resolved
getValue: () => T;

toPromise: () => LoadablePromise<T>;

// Convenience accessors
valueMaybe: () => T | void;
valueOrThrow: () => T;
errorMaybe: () => Error | void;
errorOrThrow: () => Error;
promiseMaybe: () => Promise<T> | void;
promiseOrThrow: () => Promise<T>;

map: <S>(map: (val: any) => Promise<S> | S) => Loadable<S>;
}>;

export type Loadable<T> =
| Readonly<Accessors<T> & { state: 'hasValue'; contents: T }>
| Readonly<Accessors<T> & { state: 'hasError'; contents: Error }>
| Readonly<
Accessors<T> & {
state: 'loading';
contents: LoadablePromise<T>;
}
>;
1 change: 1 addition & 0 deletions types/recoil/lib/core/node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class DefaultValue {}
21 changes: 21 additions & 0 deletions types/recoil/lib/core/recoilValue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NodeKey } from './state';

export class AbstractRecoilValue<T> {
tag: 'Writeable';
key: NodeKey;
constructor(newKey: NodeKey);
}

export class AbstractRecoilValueReadonly<T> {
tag: 'Readonly';
key: NodeKey;
constructor(newKey: NodeKey);
}

export class RecoilState<T> extends AbstractRecoilValue<T> {}
csantos42 marked this conversation as resolved.
Show resolved Hide resolved

export class RecoilValueReadOnly<T> extends AbstractRecoilValueReadonly<T> {}

export type RecoilValue<T> = RecoilValueReadOnly<T> | RecoilState<T>;

export function isRecoilValue(val: unknown): val is RecoilValue<any>;
32 changes: 32 additions & 0 deletions types/recoil/lib/core/selector.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RecoilValue, RecoilState, RecoilValueReadOnly } from './recoilValue';
import { DefaultValue } from './node';

export type GetRecoilValue = <T>(recoilVal: RecoilValue<T>) => T;
export type SetRecoilState = <T>(
recoilVal: RecoilState<T>,
newVal: T | DefaultValue | ((prevValue: T) => T | DefaultValue),
) => void;

export type ResetRecoilState = (recoilVal: RecoilState<any>) => void;

export interface ReadOnlySelectorOptions<T> {
key: string;
get: (opts: { get: GetRecoilValue }) => Promise<T> | RecoilValue<T> | T;

// cacheImplementation_UNSTABLE?: CacheImplementation<Loadable<T>>,
dangerouslyAllowMutability?: boolean;
}

export type ReadWriteSelectorOptions<T> = ReadOnlySelectorOptions<T> & {
set: (
opts: {
set: SetRecoilState;
get: GetRecoilValue;
reset: ResetRecoilState;
},
newValue: T | DefaultValue,
) => void;
};

export function selector<T>(options: ReadWriteSelectorOptions<T>): RecoilState<T>;
export function selector<T>(options: ReadOnlySelectorOptions<T>): RecoilValueReadOnly<T>;
23 changes: 23 additions & 0 deletions types/recoil/lib/core/state.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Loadable } from './loadable';

export type NodeKey = string;
csantos42 marked this conversation as resolved.
Show resolved Hide resolved
export type AtomValues = Map<NodeKey, Loadable<any>>;
export type ComponentCallback = (state: TreeState) => void;
export type TreeState = Readonly<{
// Information about the TreeState itself:
isSnapshot: boolean;
transactionMetadata: object;
dirtyAtoms: Set<NodeKey>;

// ATOMS
atomValues: AtomValues;
nonvalidatedAtoms: Map<NodeKey, unknown>;

// NODE GRAPH -- will soon move to StoreState
// Upstream Node dependencies
nodeDeps: Map<NodeKey, Set<NodeKey>>;

// Downstream Node subscriptions
nodeToNodeSubscriptions: Map<NodeKey, Set<NodeKey>>;
nodeToComponentSubscriptions: Map<NodeKey, Map<number, [string, ComponentCallback]>>;
}>;
124 changes: 124 additions & 0 deletions types/recoil/recoil-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
DefaultValue,
RecoilRoot,
atom,
selector,
useRecoilValue,
useRecoilValueLoadable,
useRecoilState,
useRecoilStateLoadable,
useSetRecoilState,
useResetRecoilState,
useRecoilCallback,
isRecoilValue,
} from 'recoil';
// import { atomFamily } from 'recoil/utils';

// DefaultValue
new DefaultValue();

// atom
const myAtom = atom({
key: 'asds',
default: 5,
});

// selector
const mySelector1 = selector({
key: 'asdfasfds',
get: () => 5,
});

const readOnlySelector = selector({
key: 'asdfasf',
get: ({ get }) => {
get(myAtom) + 10;
get(mySelector1);
get(5); // $ExpectError
},
});

const writeableSelector = selector({
key: 'asdfsadfs',
get: ({ get }) => {
get(mySelector1) + 10;
},
set: ({ get, set, reset }) => {
get(myAtom);
set(myAtom, 5);
reset(myAtom);

set(readOnlySelector, 2); // $ExpectError
reset(readOnlySelector); // $ExpectError
},
});

// RecoilRoot
RecoilRoot({});
RecoilRoot({
initializeState: ({ set, setUnvalidatedAtomValues }) => {
set(myAtom, 5);
setUnvalidatedAtomValues(new Map());

set(readOnlySelector, 2); // $ExpectError
setUnvalidatedAtomValues({}); // $ExpectError
},
});

// Hooks
useRecoilValue(myAtom);
useRecoilValue(mySelector1);
useRecoilValue(readOnlySelector);
useRecoilValue(writeableSelector);
useRecoilValue({}); // $ExpectError

useRecoilValueLoadable(myAtom);
useRecoilValueLoadable(readOnlySelector);
useRecoilValueLoadable(writeableSelector);
useRecoilValueLoadable({}); // $ExpectError

useRecoilState(myAtom);
useRecoilState(writeableSelector);
useRecoilState(readOnlySelector); // $ExpectError
useRecoilState({}); // $ExpectError

useRecoilStateLoadable(myAtom);
useRecoilStateLoadable(writeableSelector);
useRecoilStateLoadable(readOnlySelector); // $ExpectError
useRecoilStateLoadable({}); // $ExpectError

useSetRecoilState(myAtom);
useSetRecoilState(writeableSelector);
useSetRecoilState(readOnlySelector); // $ExpectError
useSetRecoilState({}); // $ExpectError

useResetRecoilState(myAtom);
useResetRecoilState(writeableSelector);
useResetRecoilState(readOnlySelector); // $ExpectError
useResetRecoilState({}); // $ExpectError

useRecoilCallback(async ({ getPromise, getLoadable, set, reset }) => {
const val: number = await getPromise(mySelector1);
const loadable = getLoadable(mySelector1);

loadable.getValue();
loadable.toPromise();
loadable.state;

set(myAtom, 5);
reset(myAtom);
});

// Other
isRecoilValue(4);
isRecoilValue(myAtom);
isRecoilValue(null);
isRecoilValue(mySelector1);

// UTILS

// atomFamily
// atomFamily({
// key: 'asdfs',
// default: (a) => {return ''},
// });
23 changes: 23 additions & 0 deletions types/recoil/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"recoil-tests.ts"
]
}
1 change: 1 addition & 0 deletions types/recoil/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "extends": "dtslint/dt.json" }