2 * ============LICENSE_START========================================================================
3 * ONAP : ccsdk feature sdnr wt odlux
4 * =================================================================================================
5 * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
6 * =================================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software distributed under the License
13 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14 * or implied. See the License for the specific language governing permissions and limitations under
16 * ============LICENSE_END==========================================================================
18 import React, { FC, useContext, createContext, useState, useEffect, useRef } from 'react';
20 import { Dispatch } from './store';
22 import { ApplicationStore, IApplicationStoreState } from '../store/applicationStore';
24 const LogLevel = +(localStorage.getItem('log.odlux.framework.flux.connect') || 0);
26 interface IApplicationStoreContext {
27 applicationStore: ApplicationStore;
30 export interface IDispatcher {
34 interface IApplicationStoreProps {
35 state: IApplicationStoreState;
38 interface IDispatchProps {
42 type ComponentDecoratorInfer<TMergedProps> = {
43 <TProps>(wrappedComponent: React.ComponentType<TProps & TMergedProps>): React.ComponentClass<Omit<TProps & TMergedProps, keyof TMergedProps>>;
46 const ApplicationStoreContext = createContext<IApplicationStoreContext | undefined>(undefined);
48 export type Connect<TMapProps extends ((...args: any) => any) | undefined = undefined, TMapDispatch extends ((...args: any) => any) | undefined = undefined> =
49 (TMapProps extends ((...args: any) => any) ? ReturnType<TMapProps> : IApplicationStoreProps) &
50 (TMapDispatch extends ((...args: any) => any) ? ReturnType<TMapDispatch> : IDispatchProps);
52 export function connect(): ComponentDecoratorInfer<IApplicationStoreProps & IDispatchProps>;
54 export function connect<TStateProps>(
55 mapStateToProps: (state: IApplicationStoreState) => TStateProps
56 ): ComponentDecoratorInfer<TStateProps & IDispatchProps>;
58 export function connect<TStateProps, TDispatchProps>(
59 mapStateToProps: (state: IApplicationStoreState) => TStateProps,
60 mapDispatchToProps: (dispatcher: IDispatcher) => TDispatchProps
61 ): ComponentDecoratorInfer<TStateProps & TDispatchProps>;
64 export function connect<TDispatchProps>(
65 mapStateToProps: undefined,
66 mapDispatchToProps: (dispatcher: IDispatcher) => TDispatchProps
67 ): ComponentDecoratorInfer<IApplicationStoreProps & TDispatchProps>;
70 export function connect<TProps, TStateProps, TDispatchProps>(
71 mapStateToProps?: ((state: IApplicationStoreState) => TStateProps),
72 mapDispatchToProps?: ((dispatcher: IDispatcher) => TDispatchProps)
74 ((WrappedComponent: React.ComponentType<TProps & (IApplicationStoreProps | TStateProps) & IDispatchProps>) => React.ComponentType<TProps>) {
76 const injectApplicationStore = (WrappedComponent: React.ComponentType<TProps & (IApplicationStoreProps | TStateProps) & IDispatchProps>): React.ComponentType<TProps> => {
78 class StoreAdapter extends React.Component<TProps, {}> {
80 render(): JSX.Element {
82 if (isWrappedComponentIsVersion1(WrappedComponent)) {
83 const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, dispatch: this.store.dispatch.bind(this.store) });
85 } else if (mapStateToProps && isWrappedComponentIsVersion2(WrappedComponent)) {
86 const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), dispatch: this.store.dispatch.bind(this.store) });
88 } else if (mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion3(WrappedComponent)) {
89 const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) });
91 } else if (!mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion4(WrappedComponent)) {
92 const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) });
95 throw new Error("Invalid arguments in connect.");
98 componentDidMount(): void {
99 this.store && this.store.changed.addHandler(this.handleStoreChanged);
102 componentWillUnmount(): void {
103 this.store && this.store.changed.removeHandler(this.handleStoreChanged);
106 private get store(): ApplicationStore {
107 return this.context.applicationStore;
110 private handleStoreChanged = () => {
114 StoreAdapter.contextType = ApplicationStoreContext;
119 return injectApplicationStore;
123 function isWrappedComponentIsVersion1(wrappedComponent: any): wrappedComponent is React.ComponentType<TProps & IApplicationStoreProps & IDispatchProps> {
124 return !mapStateToProps && !mapDispatchToProps;
127 function isWrappedComponentIsVersion2(wrappedComponent: any): wrappedComponent is React.ComponentType<TProps & TStateProps & IDispatchProps> {
128 return !!mapStateToProps && !mapDispatchToProps;
131 function isWrappedComponentIsVersion3(wrappedComponent: any): wrappedComponent is React.ComponentType<TProps & TStateProps & TDispatchProps> {
132 return !!mapStateToProps && !!mapDispatchToProps;
135 function isWrappedComponentIsVersion4(wrappedComponent: any): wrappedComponent is React.ComponentType<TProps & TStateProps & TDispatchProps> {
136 return !mapStateToProps && !!mapDispatchToProps;
140 type ApplicationStoreProviderProps = {
141 applicationStore: ApplicationStore;
144 export const ApplicationStoreProvider: FC<ApplicationStoreProviderProps> = (props) => {
145 const { applicationStore, children } = props;
148 <ApplicationStoreContext.Provider value={{ applicationStore }}>
150 </ApplicationStoreContext.Provider>
154 export const useApplicationStore = (): ApplicationStore => {
155 const context = useContext(ApplicationStoreContext);
156 if (context == null || context.applicationStore == null) {
157 throw new Error("Requires application store provider!")
159 return context.applicationStore
162 export const useSelectApplicationState = <TProp extends unknown >( selector: (state: IApplicationStoreState) => TProp, eqFunc = (a: TProp, b: TProp) => a === b ): TProp => {
163 const context = useContext(ApplicationStoreContext);
164 if (context == null || context.applicationStore == null) {
165 throw new Error("Requires application store provider!")
168 const [propState, setPropState] = useState<TProp>(selector(context.applicationStore.state));
170 const selectorRef = useRef(selector);
171 selectorRef.current = selector;
173 const propStateRef = useRef({propState});
174 propStateRef.current.propState = propState;
177 if (context == null || context.applicationStore == null) {
178 throw new Error("Requires application store provider!")
181 const changedHandler = () => {
182 const newState = selectorRef.current(context.applicationStore.state);
183 if (!eqFunc(newState, propStateRef.current.propState)) {
184 setPropState(newState);
189 console.log("useSelectApplicationState: adding handler", changedHandler);
192 context.applicationStore.changed.addHandler(changedHandler);
196 console.log("useSelectApplicationState: removing handler", changedHandler);
199 context.applicationStore.changed.removeHandler(changedHandler);
207 export const useApplicationDispatch = (): Dispatch => {
208 const context = useContext(ApplicationStoreContext);
209 if (context == null || context.applicationStore == null) {
210 throw new Error("Requires application store provider!")
212 return context.applicationStore.dispatch;