Skip to content

Instantly share code, notes, and snippets.

@zelaznik
Created October 28, 2023 15:44
Show Gist options
  • Save zelaznik/c7b9f3bb557a368370ae0605460f0d47 to your computer and use it in GitHub Desktop.
Save zelaznik/c7b9f3bb557a368370ae0605460f0d47 to your computer and use it in GitHub Desktop.
Nested header tags in React
import React from 'react';
import NestedHeader from "./NestedHeader.tsx"
function SomeChildComponent() {
return (
<NestedHeader content="Still keeps track globally of how many heading levels deep you are">
<p>
This is the power of this abstraction. Child components can use NestedHeader.
and the header level will always be correct, one level more than its parent.
</p>
</NestedHeader>
)
}
function App() {
return (
<h1>We still use the plain old HTML tag for h1. There should be only one per page.</h1>
<p>The logic is easier if we start at h2 for nested headers.</p>
<NestedHeader content="(h2) we can pass it a text-only header such as this">
<NestedHeader content={<><strong>(h3)</strong> We can also pass it a jsx value</>}>
<p>The children of the NestedHeader component will appear AFTER the h tag</p>
</NestedHeader>
<NestedHeader className="hello-world" content="Arbitrary attributes are passed along">
<p>The children of the NestedHeader component will appear AFTER the h tag</p>
</NestedHeader>
<NestedHeader content={(Hx) => (
<header>
<Hx className="foo-bar">More complicated headers</Hx>
<p>The function Hx is a React component that automatically returns h1 through h6</p>
<p>By passing a function into "content", you can be more flexible with where you place the h1-h6</p>
</header>
)}>
<p>The children of the header component will still appear AFTER the result of the function</p>
<SomeChildComponent />
</NestedHeader>
</NestedHeader>
)
}
import React, { useContext, createContext } from "react";
const NestedHeaderContext = createContext<number>(2);
type HxCallback = (component: typeof Hx) => React.ReactNode;
type HxProps = {
children: React.ReactNode;
[x: string]: any;
};
type NestedHeaderProps = {
content: string | React.ReactNode | HxCallback;
children?: React.ReactNode;
[x: string]: any;
};
export function Hx({ children, ...props }: HxProps) {
const level = useContext(NestedHeaderContext);
const tag = "h" + Math.min(level, 6);
return React.createElement(tag, props, children);
}
export default function NestedHeader({
content,
children,
...props
}: NestedHeaderProps) {
const level = useContext(NestedHeaderContext);
let Header;
if (typeof content === "function") {
Header = content(Hx);
} else {
Header = <Hx {...props}>{content}</Hx>;
}
return (
<>
{Header}
<NestedHeaderContext.Provider value={level + 1}>
{children}
</NestedHeaderContext.Provider>
</>
);
}
<html>
<body>
<div id="root">
<h1>We still use the plain old HTML tag for h1. There should be only one per page.</h1>
<p>The logic is easier if we start at h2 for nested headers.</p>
<h2>(h2) we can pass it a text-only header such as this</h2>
<h3><strong>(h3)</strong> We can also pass it a jsx value</h3>
<p>The children of the NestedHeader component will appear AFTER the h tag</p>
<!-- end H3 -->
<h3 class="hello-world">Arbitrary attributes are passed along</h3>
<p>The children of the NestedHeader component will appear AFTER the h tag</p>
<!-- end H3 -->
<header>
<h3 class="foo-bar">More complicated headers</h3>
<p>The function Hx is a React component that automatically returns h1 through h6</p>
<p>By passing a function into "content", you can be more flexible with where you place the h1-h6</p>
</header>
<p>The children of the NestedHeader component will still appear AFTER the h tag</p>
<h4>Still keeps track globally of how many heading levels deep you are</h4>
<p>
This is the power of this abstraction. Child components can use NestedHeader.
and the header level will always be correct, one level more than its parent.
</p>
<!-- end H4 -->
<!-- end H3 -->
<!-- end H2 -->
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment