@moxy/next-layout
Version:
Add persistent and nested layouts to your Next.js projects in a declarative way
79 lines (68 loc) • 3.12 kB
JavaScript
import React, { useContext, useMemo, forwardRef, useEffect, useRef } from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import useObjectState from './util/use-object-state';
import LayoutContext from './util/context';
const toFunction = fn => typeof fn === 'function' ? fn : () => fn;
const getInitialLayoutTreeSymbol = Symbol('getInitialLayoutTreeSymbol');
const withLayout = (mapLayoutStateToLayoutTree, mapPropsToInitialLayoutState) => {
const shouldInjectSetLayoutState = typeof mapLayoutStateToLayoutTree === 'function';
mapLayoutStateToLayoutTree = toFunction(mapLayoutStateToLayoutTree);
mapPropsToInitialLayoutState = toFunction(mapPropsToInitialLayoutState);
return Component => {
const WithLayout = forwardRef((_props, ref) => {
const {
pageKey,
props
} = useMemo(() => {
const {
pageKey,
...props
} = _props;
return {
pageKey,
props
};
}, [_props]);
const initialLayoutStateRef = useRef();
if (!initialLayoutStateRef.current) {
initialLayoutStateRef.current = mapPropsToInitialLayoutState(props);
}
const layoutProviderValue = useContext(LayoutContext); // Check if <LayoutTree /> was not added to the app (missing provider).
if (process.env.NODE_ENV !== 'production' && !layoutProviderValue) {
throw new Error('It seems you forgot to include <LayoutTree /> in your app');
}
const {
updateLayoutTree,
Component: ProviderComponent,
pageKey: providerPageKey
} = layoutProviderValue;
const [layoutState, setLayoutState] = useObjectState(initialLayoutStateRef.current);
useEffect(() => {
if (layoutState !== initialLayoutStateRef.current && ProviderComponent === WithLayout && providerPageKey === pageKey) {
updateLayoutTree(mapLayoutStateToLayoutTree(layoutState));
}
}, [layoutState, updateLayoutTree, ProviderComponent, providerPageKey, pageKey]);
return useMemo(() => /*#__PURE__*/React.createElement(Component, Object.assign({
ref: ref
}, shouldInjectSetLayoutState ? {
setLayoutState
} : {}, props)), [ref, setLayoutState, props]);
});
const getInitialLayoutTree = props => {
const layoutState = mapPropsToInitialLayoutState(props);
return mapLayoutStateToLayoutTree(layoutState);
};
Object.defineProperty(WithLayout, getInitialLayoutTreeSymbol, {
value: getInitialLayoutTree
});
WithLayout.displayName = `WithLayout(${Component.displayName || Component.name || 'Component'})`;
hoistNonReactStatics(WithLayout, Component);
return WithLayout;
};
};
export const getInitialLayoutTree = (Component, pageProps) => {
var _Component$getInitial;
return (_Component$getInitial = Component[getInitialLayoutTreeSymbol]) === null || _Component$getInitial === void 0 ? void 0 : _Component$getInitial.call(Component, pageProps);
};
export const isComponentWrapped = Component => !!Component[getInitialLayoutTreeSymbol];
export default withLayout;