From da4e11734601ec4b4df074dbf5ca67839b524988 Mon Sep 17 00:00:00 2001 From: Vincent Siao Date: Wed, 23 May 2018 19:43:54 -0700 Subject: [PATCH] Add typings for create-subscription --- .../create-subscription-tests.tsx | 119 ++++++++++++++++++ types/create-subscription/index.d.ts | 44 +++++++ types/create-subscription/tsconfig.json | 24 ++++ types/create-subscription/tslint.json | 1 + 4 files changed, 188 insertions(+) create mode 100644 types/create-subscription/create-subscription-tests.tsx create mode 100644 types/create-subscription/index.d.ts create mode 100644 types/create-subscription/tsconfig.json create mode 100644 types/create-subscription/tslint.json diff --git a/types/create-subscription/create-subscription-tests.tsx b/types/create-subscription/create-subscription-tests.tsx new file mode 100644 index 0000000000..891adc5ede --- /dev/null +++ b/types/create-subscription/create-subscription-tests.tsx @@ -0,0 +1,119 @@ +import * as React from "react"; +import { createSubscription, Subscription } from "create-subscription"; + +// +// Example: Subscribing to event dispatchers +// https://github.com/facebook/react/blob/fa7fa812c70084e139d13437fb204fcdf9152299/packages/create-subscription/README.md#subscribing-to-event-dispatchers +// ---------------------------------------------------------------------------- + +// Start with a simple component. +// In this case, it's a functional component, but it could have been a class. +function FollowerComponent({ followersCount }: { followersCount: number }) { + return
You have {followersCount} followers!
; +} + +interface EventDispatcher { + value: T; + addEventListener(eventName: "change", onChange: (newValue: T) => any): void; + removeEventListener(eventName: "change", onChange: (newValue: T) => any): void; +} + +// Create a wrapper component to manage the subscription. +// $ExpectType Subscription, number> +const EventHandlerSubscription = createSubscription({ + getCurrentValue: (eventDispatcher: EventDispatcher) => eventDispatcher.value, + subscribe: (eventDispatcher: EventDispatcher, callback) => { + const onChange = (event: any) => callback(eventDispatcher.value); + eventDispatcher.addEventListener("change", onChange); + return () => eventDispatcher.removeEventListener("change", onChange); + } +}); + +declare const eventDispatcher: EventDispatcher; + +// Your component can now be used as shown below. +// In this example, 'eventDispatcher' represents a generic event dispatcher. + + {value => { + // $ExpectType number + const followersCount = value; + return ; + }} +; + +// +// Example: Subscribing to event dispatchers +// https://github.com/facebook/react/blob/fa7fa812c70084e139d13437fb204fcdf9152299/packages/create-subscription/README.md#subscribing-to-a-promise +// ---------------------------------------------------------------------------- + +// Start with a simple component. +const LoadingComponent: React.SFC<{ loadingStatus: string | undefined }> = ({ loadingStatus }) => { + if (loadingStatus === undefined) { + // Loading + } else if (loadingStatus === null) { + // Error + } else { + // Success + } + return null; +}; + +// Wrap the functional component with a subscriber HOC. +// This HOC will manage subscriptions and pass values to the decorated component. +// It will add and remove subscriptions in an async-safe way when props change. +// $ExpectType Subscription, string | undefined> +const PromiseSubscription = createSubscription({ + getCurrentValue: promise => { + // There is no way to synchronously read a Promise's value, + // So this method should return undefined. + return undefined; + }, + subscribe: (promise: Promise, callback: (newValue: string | undefined) => void) => { + promise.then( + // Success + callback, + // Failure + () => callback(undefined) + ); + + // There is no way to "unsubscribe" from a Promise. + // create-subscription will still prevent stale values from rendering. + return () => {}; + } +}); + +declare const loadingPromise: Promise; + +// Your component can now be used as shown below. + + {value => { + // $ExpectType string | undefined + const loadingStatus = value; + return ; + }} +; + +// +// Error cases +// ---------------------------------------------------------------------------- + +declare const wrongPromise: Promise; + +// $ExpectError + + {value => null} +; + +// $ExpectError +const MismatchSubscription = createSubscription({ + getCurrentValue: (a: number) => null, + subscribe: (a: string, callback) => (() => undefined) +}); + +// $ExpectError +const NoUnsubscribe = createSubscription({ + getCurrentValue: (a: number) => a, + subscribe: (a: number, callback) => { + // oops, should've returned a callback here + } +}); diff --git a/types/create-subscription/index.d.ts b/types/create-subscription/index.d.ts new file mode 100644 index 0000000000..28cfbd4e31 --- /dev/null +++ b/types/create-subscription/index.d.ts @@ -0,0 +1,44 @@ +// Type definitions for create-subscription 16.4 +// Project: https://github.com/facebook/react/tree/master/packages/create-subscription +// Definitions by: Asana +// Vincent Siao +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 + +import * as React from "react"; + +export interface SubscriptionProps { + children: (value: T) => React.ReactNode; + source: S; +} + +export interface SubscriptionState { + source: S; + value: T; +} + +export interface Subscription extends React.ComponentClass> {} +export type Unsubscribe = () => any; + +export interface SubscriptionConfig { + /** + * Synchronously gets the value for the subscribed property. + * Return undefined if the subscribable value is undefined, + * Or does not support synchronous reading (e.g. native Promise). + */ + getCurrentValue: (source: S) => T; + + /** + * Set up a subscription for the subscribable value in props, and return an unsubscribe function. + * Return false to indicate the property cannot be unsubscribed from (e.g. native Promises). + * Due to the variety of change event types, subscribers should provide their own handlers. + * Those handlers should not attempt to update state though; + * They should call the callback() instead when a subscription changes. + */ + subscribe: ( + source: S, + callback: (newValue: T) => void + ) => Unsubscribe; +} + +export function createSubscription(config: SubscriptionConfig): Subscription; diff --git a/types/create-subscription/tsconfig.json b/types/create-subscription/tsconfig.json new file mode 100644 index 0000000000..0e42cd3adb --- /dev/null +++ b/types/create-subscription/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "jsx": "react", + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "create-subscription-tests.tsx" + ] +} diff --git a/types/create-subscription/tslint.json b/types/create-subscription/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/create-subscription/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }