import * as React from 'react' import { Subscription } from 'rxjs' import { createStore, EffectsAs, Store, StoreDefinition, StoreSnapshot } from '..' import { Diff, getDisplayName, mapValues } from '../utils' export type ConnectAs< States extends { [alias: string]: any } > = { Container: React.ComponentType> useStores(): { [K in keyof States]: Store } withStores: }>( Component: React.ComponentType ) => React.ComponentType< Diff }> > } export type ContainerPropsAs< States extends { [alias: string]: any } > = { effects?: EffectsAs initialStates?: States } export function createConnectedStoreAs< States extends { [alias: string]: any } >(initialStates: States, effects?: EffectsAs): ConnectAs { let Context = React.createContext({ __MISSING_PROVIDER__: true } as any) type ContainerState = { storeDefinitions: { [K in keyof States]: StoreDefinition | null } storeSnapshots: { [K in keyof States]: StoreSnapshot | null } subscriptions: { [K in keyof States]: Subscription } } class Container extends React.Component< ContainerPropsAs, ContainerState > { constructor(props: ContainerPropsAs) { super(props) // Create store definition from initial state let states = props.initialStates || initialStates let stores = mapValues(states, _ => createStore(_)) // Apply effects? let fx = props.effects || effects if (fx) { fx(stores) } this.state = { storeDefinitions: stores, storeSnapshots: mapValues(stores, _ => _.getCurrentSnapshot()), subscriptions: mapValues(stores, (_, k) => _.onAll().subscribe(() => this.setState(state => ({ storeSnapshots: Object.assign({}, state.storeSnapshots, { [k]: _.getCurrentSnapshot() }) })) ) ) } } componentWillUnmount() { mapValues(this.state.subscriptions, _ => _.unsubscribe()) // Let the state get GC'd. // TODO: Find a more elegant way to do this. if (this.state.storeSnapshots) { } mapValues(this.state.storeSnapshots, _ => ((_ as any).state = null)) mapValues( this.state.storeSnapshots, _ => ((_ as any).storeDefinition = null) ) mapValues( this.state.storeDefinitions, _ => ((_ as any).storeSnapshot = null) ) } render() { return ( {this.props.children} ) } } let Consumer = (props: { children: ( stores: { [K in keyof States]: StoreSnapshot } ) => JSX.Element displayName: string }) => ( {stores => { if (!isInitialized(stores)) { 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 createConnectedStoreAs().` ) } return props.children(stores) }} ) function withStores< Props extends { [K in keyof States]: Store }, PropsWithoutStore = Diff }> >( Component: React.ComponentType ): React.ComponentType { let displayName = getDisplayName(Component) let f: React.StatelessComponent = props => ( {stores => } ) f.displayName = `withStores(${displayName})` return f } return { Container, useStores() { return React.useContext(Context) }, withStores } } function isInitialized( store: StoreSnapshot | { __MISSING_PROVIDER__: true } ) { return !('__MISSING_PROVIDER__' in store) }