import * as React from 'react' import { Subscription } from 'rxjs' import { createStore, Effects, Store, StoreDefinition, StoreSnapshot } from '..' import { Diff, getDisplayName } from '../utils' export type Connect = { Container: React.ComponentType> useStore(): Store withStore: }>( Component: React.ComponentType ) => React.ComponentType }>> } export type ContainerProps = { effects?: Effects initialState?: State } export function createConnectedStore( initialState: State, effects?: Effects ): Connect { let Context = React.createContext({ __MISSING_PROVIDER__: true } as any) type ContainerState = { storeSnapshot: StoreSnapshot } class Container extends React.Component< ContainerProps, ContainerState > { subscription: Subscription storeDefinition: StoreDefinition constructor(props: ContainerProps) { super(props) // Create store definition from initial state let state = props.initialState || initialState this.storeDefinition = createStore(state) // Apply effects? let fx = props.effects || effects if (fx) { fx(this.storeDefinition) } this.state = { storeSnapshot: this.storeDefinition.getCurrentSnapshot() } this.subscription = this.storeDefinition.onAll().subscribe(() => this.setState({ storeSnapshot: this.storeDefinition.getCurrentSnapshot() }) ) } componentWillUnmount() { this.subscription.unsubscribe() // Let the state get GC'd. // TODO: Find a more elegant way to do this. ; (this.storeDefinition as any).storeSnapshot = null ; (this as any).storeDefinition = null } render() { return ( {this.props.children} ) } } let Consumer = (props: { children: (store: StoreSnapshot) => JSX.Element displayName: string }) => ( {store => { if (!isInitialized(store)) { throw Error( `[Undux] Component "${ props.displayName }" does not seem to be nested in an Undux . To fix this error, be sure to render the component in the ... component that you got back from calling createConnectedStore().` ) } return props.children(store) }} ) function withStore< Props extends { store: Store }, PropsWithoutStore = Diff }> >( Component: React.ComponentType ): React.ComponentType { let displayName = getDisplayName(Component) let f: React.StatelessComponent = props => ( {storeSnapshot => } ) f.displayName = `withStore(${displayName})` return f } return { Container, useStore() { return React.useContext(Context) }, withStore } } function isInitialized( store: StoreSnapshot | { __MISSING_PROVIDER__: true } ) { return !('__MISSING_PROVIDER__' in store) }