Skip to content

Commit

Permalink
feat: add pageKey
Browse files Browse the repository at this point in the history
  • Loading branch information
satazor committed Aug 24, 2020
1 parent 0bd8f13 commit c31aace
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 28 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ Type: `object`

The page component props, which maps to your App `pageProps` prop.

#### pageKey?

Type: `string`

The page key used to uniquely identify this page. Useful for dynamic routes, where the `Component` is the same, but you still want the page to be re-mounted. For such cases, you may use `router.asPath.replace(/\?.+/, '')`.

#### defaultLayout

Type: `ReactElement`
Expand Down
15 changes: 9 additions & 6 deletions src/LayoutTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class LayoutTree extends PureComponent {
static propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object,
pageKey: PropTypes.string,
defaultLayout: PropTypes.element,
children: PropTypes.func,
};
Expand All @@ -20,13 +21,14 @@ export default class LayoutTree extends PureComponent {
};

static getDerivedStateFromProps(props, state) {
const { Component, pageProps } = props;
const { Component, pageProps, pageKey } = props;

const didPageChange = props.Component !== state.Component;
const didPageChange = Component !== state.Component || pageKey !== state.pageKey;
const layoutTree = didPageChange ? getInitialLayoutTree(Component, pageProps) : state.layoutTree;

return {
Component,
pageKey,
layoutTree,
};
}
Expand All @@ -36,18 +38,19 @@ export default class LayoutTree extends PureComponent {
// eslint-disable-next-line react/sort-comp
updateLayoutTree = (layoutTree) => this.setState({ layoutTree });

getProviderValue = memoizeOne((Component) => ({
getProviderValue = memoizeOne((Component, pageKey) => ({
Component,
pageKey,
updateLayoutTree: this.updateLayoutTree,
}));

render() {
const { defaultLayout, Component, pageProps, children: render } = this.props;
const { defaultLayout, Component, pageProps, pageKey, children: render } = this.props;
const { layoutTree } = this.state;

const page = <Component { ...pageProps } />;
const page = <Component { ...pageProps } key={ pageKey } pageKey={ pageKey } />;
const fullTree = createFullTree(layoutTree ?? defaultLayout, page);
const providerValue = this.getProviderValue(Component);
const providerValue = this.getProviderValue(Component, pageKey);

return (
<LayoutProvider value={ providerValue }>
Expand Down
17 changes: 13 additions & 4 deletions src/with-layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ const withLayout = (mapLayoutStateToLayoutTree, mapPropsToInitialLayoutState) =>
mapPropsToInitialLayoutState = toFunction(mapPropsToInitialLayoutState);

return (Component) => {
const WithLayout = forwardRef((props, ref) => {
const WithLayout = forwardRef((_props, ref) => {
const { pageKey, props } = useMemo(() => {
const { pageKey, ...props } = _props;

return { pageKey, props };
}, [_props]);

const initialLayoutStateRef = useRef();

if (!initialLayoutStateRef.current) {
Expand All @@ -28,14 +34,17 @@ const withLayout = (mapLayoutStateToLayoutTree, mapPropsToInitialLayoutState) =>
throw new Error('It seems you forgot to include <LayoutTree /> in your app');
}

const { updateLayoutTree, Component: ProviderComponent } = layoutProviderValue;
const { updateLayoutTree, Component: ProviderComponent, pageKey: providerPageKey } = layoutProviderValue;
const [layoutState, setLayoutState] = useObjectState(initialLayoutStateRef.current);

useEffect(() => {
if (layoutState !== initialLayoutStateRef.current && ProviderComponent === WithLayout) {
if (layoutState !== initialLayoutStateRef.current &&
ProviderComponent === WithLayout &&
providerPageKey === pageKey
) {
updateLayoutTree(mapLayoutStateToLayoutTree(layoutState));
}
}, [layoutState, updateLayoutTree, ProviderComponent]);
}, [layoutState, updateLayoutTree, ProviderComponent, providerPageKey, pageKey]);

return useMemo(() => (
<Component
Expand Down
172 changes: 168 additions & 4 deletions test/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,51 @@ exports[`should allow passing a custom render prop to LayoutTree 2`] = `
</PrimaryLayout>
`;

exports[`should not update layout if page is not the active Component of LayoutTree 1`] = `
exports[`should ignore setLayoutState calls if Component's pageKey is not the active pageKey of LayoutTree 1`] = `
<LayoutTree
Component={
Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "WithLayout(Foo)",
"render": [Function],
}
}
pageKey="foo"
>
<PrimaryLayout
variant="light"
>
<main>
<WithLayout(Foo)
key="foo"
pageKey="foo"
>
<Foo
setLayoutState={[Function]}
>
<h1>
Foo
</h1>
</Foo>
</WithLayout(Foo)>
</main>
</PrimaryLayout>
<WithLayout(Foo)
foo={true}
>
<Foo
foo={true}
setLayoutState={[Function]}
>
<h1>
Foo
</h1>
</Foo>
</WithLayout(Foo)>
</LayoutTree>
`;

exports[`should ignore setLayoutState calls if page is not the active Component of LayoutTree 1`] = `
<LayoutTree
Component={
Object {
Expand All @@ -51,10 +95,14 @@ exports[`should not update layout if page is not the active Component of LayoutT
}
}
>
<PrimaryLayout>
<PrimaryLayout
variant="light"
>
<main>
<WithLayout(Home)>
<Home>
<Home
setLayoutState={[Function]}
>
<h1>
Home
</h1>
Expand All @@ -67,7 +115,7 @@ exports[`should not update layout if page is not the active Component of LayoutT
setLayoutState={[Function]}
>
<h1>
Home
Foo
</h1>
</Foo>
</WithLayout(Foo)>
Expand Down Expand Up @@ -250,3 +298,119 @@ exports[`should render no layout 1`] = `
</Home>
</LayoutTree>
`;

exports[`should update the layout tree correctly if Component changes 1`] = `
<LayoutTree
Component={
Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "WithLayout(Home)",
"render": [Function],
}
}
>
<PrimaryLayout
variant="light"
>
<main>
<WithLayout(Home)>
<Home>
<h1>
Home
</h1>
</Home>
</WithLayout(Home)>
</main>
</PrimaryLayout>
</LayoutTree>
`;

exports[`should update the layout tree correctly if Component changes 2`] = `
<LayoutTree
Component={
Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "WithLayout(Foo)",
"render": [Function],
}
}
>
<PrimaryLayout
variant="dark"
>
<main>
<WithLayout(Foo)>
<Foo>
<h1>
Foo
</h1>
</Foo>
</WithLayout(Foo)>
</main>
</PrimaryLayout>
</LayoutTree>
`;

exports[`should update the layout tree correctly if pageKey changes 1`] = `
<LayoutTree
Component={
Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "WithLayout(Home)",
"render": [Function],
}
}
pageKey="foo"
>
<PrimaryLayout
variant="dark"
>
<main>
<WithLayout(Home)
key="foo"
pageKey="foo"
>
<Home
setLayoutState={[Function]}
>
<h1>
Home
</h1>
</Home>
</WithLayout(Home)>
</main>
</PrimaryLayout>
</LayoutTree>
`;

exports[`should update the layout tree correctly if pageKey changes 2`] = `
<LayoutTree
Component={
Object {
"$$typeof": Symbol(react.forward_ref),
"displayName": "WithLayout(Home)",
"render": [Function],
}
}
pageKey="bar"
>
<PrimaryLayout
variant="light"
>
<main>
<WithLayout(Home)
key="bar"
pageKey="bar"
>
<Home
setLayoutState={[Function]}
>
<h1>
Home
</h1>
</Home>
</WithLayout(Home)>
</main>
</PrimaryLayout>
</LayoutTree>
`;

0 comments on commit c31aace

Please sign in to comment.