Skip to content

Commit

Permalink
chore: create connect
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz committed Sep 14, 2020
1 parent 875fec7 commit 201f892
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 4 deletions.
25 changes: 25 additions & 0 deletions src/createConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { connect } from "./utils/connect";

export function createConnect<S, I, T extends string, P, K>(
Context?: Context<S, T, P>,
) {
return function connectContext(
mapStateToProps?: MapStateToProps<S, I, K>,
mapDispatchToProps?:
| MapDispatchToProps<T, P, I>
| ActionCreatorMapping<T, P, S>,
mergeProps?: (
stateProps?: K,
dispatchProps?: ActionDispatcherMapping<T, P>,
ownProps?: I,
) => I & K & ActionDispatcherMapping<T, P>,
options?: ConnectOptions<S, T, P, I, K>,
): (
component: ReactComponent<I & K & ActionDispatcherMapping<T, P>>,
) => React.FunctionComponent<I> {
return connect(mapStateToProps, mapDispatchToProps, mergeProps, {
...options,
context: Context,
});
};
}
8 changes: 4 additions & 4 deletions src/utils/bindActionCreators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ export function bindActionCreator<T extends string, P>(
* same keys, but with every function wrapped into a `dispatch` call so they
* may be invoked directly.
*/
export default function bindActionCreators<T extends string, P>(
export function bindActionCreators<T extends string, P>(
actionCreator: ActionCreator<T, P>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcher<P>;

export default function bindActionCreators<T extends string, P>(
export function bindActionCreators<T extends string, P>(
actionCreator: ActionCreator<T, P>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcher<P>;

export default function bindActionCreators<T extends string, P, S>(
export function bindActionCreators<T extends string, P, S>(
actionCreators: ActionCreatorMapping<T, P, S>,
dispatch: ContextDispatch<T, P>,
): ActionDispatcherMapping<T, P>;

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default function bindActionCreators(
export function bindActionCreators(
actionCreators: ActionCreator | ActionCreatorMapping,
dispatch: ContextDispatch,
) {
Expand Down
95 changes: 95 additions & 0 deletions src/utils/connect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as React from "react";
import { GlobalContext } from "src/components/Context";
import { bindActionCreators } from "./bindActionCreators";

function defaultMergeProps<T extends string, P, I, K, J>(
stateProps?: K,
dispatchProps?: ActionDispatcherMapping<T, P>,
ownProps?: I,
): J {
return ({ ...ownProps, ...stateProps, ...dispatchProps } as unknown) as J;
}

function isFunction<F>(maybeFunction: F | unknown): maybeFunction is F {
return typeof maybeFunction === "function";
}

function asMapDispatchToPropsFn<S, T extends string, P, I>(
actionCreators?: ActionCreatorMapping<T, P, S>,
): MapDispatchToProps<T, P, I> | undefined {
return (
actionCreators &&
function mapDispatchToProps(
dispatch: ContextDispatch<T, P>,
): ActionDispatcherMapping<T, P> {
return bindActionCreators(actionCreators, dispatch);
}
);
}

export function connect<
S,
I,
T extends string,
P,
K,
J extends I & K & ActionDispatcherMapping<T, P>
>(
mapStateToProps?: MapStateToProps<S, I, K>,
mapDispatchToProps?:
| MapDispatchToProps<T, P, I, ActionDispatcherMapping<T, P>>
| ActionCreatorMapping<T, P, S>,
mergeProps?: (
stateProps?: K,
dispatchProps?: ActionDispatcherMapping<T, P>,
ownProps?: I,
) => J,
options?: ConnectOptions<S, T, P, I, K, J>,
): (component: ReactComponent<J>) => React.FunctionComponent<I> {
const mapDispatchToPropsFn = isFunction<MapDispatchToProps<T, P, I>>(
mapDispatchToProps,
)
? mapDispatchToProps
: asMapDispatchToPropsFn(mapDispatchToProps);

return function wrapWithConnect(
componentToWrap: ReactComponent<J>,
): React.FunctionComponent<I> {
const WrappedComponent = (options?.pure
? React.memo(componentToWrap)
: componentToWrap) as ReactComponent<J>;
const wrappedComponentName =
componentToWrap.displayName || componentToWrap.name || "Component";
const displayName = `Connect(${wrappedComponentName})`;

function WrapperComponent(
props: I,
): React.ReactElement<J, typeof WrappedComponent> {
const { state, dispatch } = React.useContext(
options?.context ?? GlobalContext,
);
const stateProps = React.useMemo(
() => mapStateToProps?.(state, props),
[props, state],
);
const dispatchProps = React.useMemo(
() => mapDispatchToPropsFn?.(dispatch, props),
[dispatch, props],
);
const mergedProps = React.useMemo(
() =>
(mergeProps ?? defaultMergeProps)(
stateProps,
dispatchProps,
props,
),
[dispatchProps, props, stateProps],
);
return <WrappedComponent {...mergedProps} />;
}

WrapperComponent.WrappedComponent = WrappedComponent;
WrapperComponent.displayName = displayName;
return WrapperComponent;
};
}
42 changes: 42 additions & 0 deletions typings/connect.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
type MapStateToProps<S = unknown, I = unknown, J = I> = (
state: S,
ownProps?: I,
) => J;

type MapDispatchToProps<
T extends string,
P = unknown,
I extends Record = Record,
J extends ActionDispatcherMapping<T, P> = ActionDispatcherMapping<T, P>
> = (dispatch: ContextDispatch<T, P>, ownProps?: I) => J;

type MergeProps<
T extends string = string,
P = unknown,
I extends Record = Record,
K extends Record = Record,
J extends Record = I & K & ActionDispatcherMapping<T, P>
> = (
stateProps?: K,
dispatchProps?: ActionDispatcherMapping<T, P>,
ownProps?: I,
) => J;

type ConnectOptions<
S = unknown,
T extends string = string,
P = unknown,
I extends Record = Record, // Component own props
K extends Record = Record, // Mapped state props
J extends Record = I & K & ActionDispatcherMapping<T, P> // Merged props i.e. own & state & mapped dispatch props
> = {
areMergedPropsEqual?: (next: J, prev: J) => boolean;
areOwnPropsEqual?: (next: I, prev: I) => boolean;
areStatePropsEqual?: (next: K, prev: K) => boolean;
areStatesEqual?: (next: S, prev: S) => boolean;
context?: Context<S, T, P>;
forwardRef?: boolean;
pure?: boolean;
};

type ReactComponent<I> = React.FunctionComponent<I> | React.ComponentClass<I>;

0 comments on commit 201f892

Please sign in to comment.