· 6 years ago · Oct 15, 2019, 06:00 AM
1import { cloneElement, useMemo } from 'react';
2import classNames from 'classnames';
3
4interface CloneElementProps {
5 element: any | null;
6 children?: any;
7}
8
9/**
10 * CloneElement is a wrapper component for createElement function.
11 * This allows you to describe your cloning element declaratively
12 * which is a more natural API for React.
13 */
14export function CloneElement<T = any>({ element, children, ...rest }: CloneElementProps & Partial<T>) {
15 const getProjectedProps = useMemo(() => props => {
16 const childProps = element.props;
17
18 return Object.keys(props).reduce((acc, key) => {
19 const prop = props[key];
20 const childProp = childProps[key];
21
22 if (typeof prop === 'function' && typeof childProp === 'function') {
23 acc[key] = args => {
24 prop(args);
25 childProp(args);
26 };
27 } else if (key === 'className') {
28 acc[key] = classNames(prop, childProp);
29 } else {
30 acc[key] = prop;
31 }
32
33 return acc;
34 }, {});
35 }, [rest]);
36
37 if (element === null) {
38 return children;
39 }
40
41 const newProps = getProjectedProps(rest);
42 return cloneElement(element, {
43 ...element.props,
44 ...newProps,
45 children
46 });
47}