UNPKG

@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
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;