Unwrapping Reacts Core; access JSX.IntrinsicElement props globally (no imports required)
Tags: typescript, nextjs, react, programming
Published: 2022-03-24 08:30:33

## Update -- April 1st, 2022

The act of articulating my thoughts from writing this post sparked additional ideas centered around making this into an official npm package. The first iteration of [unwrap-react](https://www.npmjs.com/package/unwrap-react) was published on March 28th; one to two dozen iterations later, the package features full support for js-only users and excellent support for typescript users. Check it out [here](https://www.npmjs.com/package/unwrap-react)! There will be a part II outlining the ins and outs of using the package in various contexts in weeks to come, but the Readme on [GitHub](https://github.com/DopamineDriven/unwrap) or on the [official npm site](https://www.npmjs.com/package/unwrap-react) itself offer a good start in the meantime!

![unwrap-react-npm-package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nbr1g18sgmhr5tjb20z.png)

---

## How it Started

While working with forms in a current [Next + Headless WordPress build](https://github.com/DopamineDriven/nextjs-headless-wp) I found myself wanting to access, for example, the props of an `<input />` or a `<button />` or an `<a />` and so on and so forth using as little effort as possible. This resulted in many trial and error attempts at piecing together an intuitive, comprehensive, globally consumable solution (intellisense from the current working versions use with `<main />` -- pictured below):

![ReactUnwrapped-with-main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fkc3nhzb2l7aeqeskot.png)

That said, and as one might expect, there were a number of partial-solutions preceding the current singular "one-size-fits-all" solution. 

### Defining an Objective

The ideal solution in my mind was two-fold: (1) global re-usability, project-agnostic, adhere to native (React namespace) type definition usage; (2) piece something together that even the most "cowabunga" of js-only developers could get behind -- _without_ compromising on type safety at any time.


![power-cowabunga-js-user-in-a-nutshell](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wbdstez7xgehfzzce1t8.png)

## Phase One -- Generics = <3
If your aim involves tackling bigger picture, project-wide, type-related goals then generics are likely already an integral part of your arsenal. However, if you haven't worked with generics before then you're in luck! The remainder of this post deals with types that heavily rely on generic properties. You could say it's a _typeof_ acquired taste, one that grows on you from the moment your first generic expression "clicks".

![archer-bloody-mary-blessed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8knc3qq5fo2m8rwwdv1t.gif)

### Generics -- a brief Primer
If you're already familiar with using generics feel free to skip ahead to the next section. If not, let's dive right in!

#### A Simple Generic Starter - Unwrapping a Promise


```ts
// disambiguation: see line 1482 of node_modules/typescript/lib/lib.es5.d.ts for info on Promise vs PromiseLike
export type UnwrapPromise<T> = T extends
  | PromiseLike<infer U>
  | Promise<infer U>
  ? U
  : T;
```
You might be asking yourself something along the lines of "_When_ the hell exactly is this type useful? _How_ is it useful? _Why_ is it useful? In _What_ contexts is it most useful?" which are great questions to consider. Scrutiny is a beautiful thing.

To address these hypothetical questions which you may or may not be asking yourself, the `UnwrapPromise<T>` type is exceedingly useful when it comes to _inferring the return type of an async function (a promise)_

Think of seeding files that return data with a whole lot going on in the context of types -- [often manifesting as a single 1,000+ line async function in practice](https://github.com/DopamineDriven/next-prisma/blob/main/src/seeds/seed.ts#L1194). Sounds like a royal pain in the ass to statically type out, right? Right. It absolutely would be -- _but it can be tackled in a couple lines of clean generic-laced code_ -- let's approach this using our simple generic from above as the cornerstone (code snippets from another recent project [linked here & within this paragraph above](https://github.com/DopamineDriven/next-prisma/blob/main/src/seeds/seed.ts)):

```ts
export async function seed<T extends import("@prisma/client").PrismaClient>(
  prisma: T
) {
// line 5 -- lots of data mimicry unfolds below
// ...
// line 1067 -- the main event
 const seedUser = async () => {
    return await prisma.user.create({
      data: {
       // schema.prisma models/types seeded here
      },
      include: {
        sessions: true,
        accounts: true,
        profile: true,
        entries: true,
        _count: true,
        comments: true
      }
    });
  };
  return seedUser();
} // line 1,193 -- let's unwrap this sizeable beast

// similar to the type we defined previously
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// use built-in ReturnType<T> inside of UnwrapPromise<T>
type SeedInferred = UnwrapPromise<ReturnType<typeof seed>>;

// enhance precision by extending Record<keyof U, infer U>

type SeedPropsInferred<U> = UnwrapPromise<
  typeof seed extends Record<keyof U, infer U>
    ? Record<keyof U, U>
    : UnwrapPromise<typeof seed>
>;
```

Two of the three generics that we just defined are integral parts of the `main` function below. The latter function calls on the lengthy seed function when a `yarn seed` script is executed in the terminal. This prompts the `seed` function to trigger, generating quasi-random data for a new user which is ultimately persisted by MongoDB Atlas for the particular repo in question.  

At any rate, let's see how the return type of `seed` is inferred via the `SeedInferred` type which lets us know the exact shape of our data on success:

to circle back, this type

```ts
type SeedInferred = UnwrapPromise<ReturnType<typeof seed>>;
```

Extracts the following definition

![Seed-Inferred-Via-Unwrap-Promise](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fx4zry8mjwwaj7p1ekg.png)

Likewise, the "deepening" generic -- `SeedPropsInferred` -- extracts the type(s) employed by the targeted async function `seed`. 

The `SeedPropsInferred<U>` type

```ts
type SeedPropsInferred<U> = UnwrapPromise<
  typeof seed extends Record<keyof U, infer U>
    ? Record<keyof U, U>
    : UnwrapPromise<typeof seed>
>;
```

Extracts the following definition for props used in the async `seed` function:

```ts
type SeedPropsInferred<U> = (<T extends PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined>>(prisma: T) => Promise<...> extends Record<...> ? Record<...> : <T extends PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined>>(prisma: T) => Promise<...>) extends Promise<...> ? U : <T extends PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined>>(prisma: T) => Promise<...> extends Record<...> ? Record<...> : <T extends PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined>>(prisma: T) => Promise<...>
```

Both `SeedInferred` and `SeedPropsInferred` are of utility when it comes to typing out the `main` function -- minimal effort, maximal results. Generics are powerful, the tradeoff being embracing additional complexity/abstraction for typesafe codebases and far fewer lines of code written

```ts
async function main() {
  const prisma = await import("../server/Context/prisma");
  try {
    await prisma.default
      .$connect()
      .then(() => console.log("[seeding]: db connection opened"));
    const s: SeedPropsInferred<{
      props: typeof prisma;
    }> = async (): Promise<SeedInferred> =>
      await seed(prisma.default).then(data => {
        console.log(
          JSON.stringify(
            `[seeding]: success 🎉 created ${data.role} with id ${data.id} and email ${data.email}`,
            null,
            2
          )
        );
        return data;
      });
    return await s(prisma.default);
  } catch (err) {
    console.error(err);
    process.exitCode = 1;
  } finally {
    return await prisma.default
      .$disconnect()
      .then(() => console.log(`[seeding]: db connection closed`));
  }
}

main();
```
The seed-invoking function, main, has the following shape according to TS Intellisense:

```ts
function main(): Promise<void | (User & {
    profile: Profile | null;
    accounts: Account[];
    sessions: Session[];
    entries: Entry[];
    comments: Comment[];
    _count: Prisma.UserCountOutputType;
})>
```

That said, the following snippet is arguably the most important to grapple with in the context of understanding type inference:
```ts
    const s: SeedPropsInferred<{
      props: typeof prisma;
    }> = async (): Promise<SeedInferred> =>
      await seed(prisma.default).then(data => {
        console.log(
          JSON.stringify(
            `[seeding]: success 🎉 created ${data.role} with id ${data.id} and email ${data.email}`,
            null,
            2
          )
        );
        return data;
      });
    return await s(prisma.default);
```
Why? Well, we know that the `seed` function takes `prisma: PrismaClient` as a prop, so it's only its return type(s) that would otherwise be a mystery. That, and it would be reasonable to assume that the success value returned by the `main` instantiating function mirrors that of the value returned by the `seed` function (it does). With the above logic in place intellisense doesn't miss a beat and perfectly infers the shape(s) of the returned data.

To illustrate that this does indeed work as intended, here is the cli output from executing the `yarn seed` script:

```bash
dopamine_driven@LAPTOP-2IH011V4:~/personal/port/next-prisma/next-prisma$ yarn seed
yarn run v1.22.18
$ ts-node src/seeds/seed.ts
[seeding]: db connection opened
"[seeding]: success 🎉 created USER with id 623d0997f9677901309ee6f8 and email kattie.gislason@gmail.com
[seeding]: db connection closed
Done in 5.36s.
```
This entry now exists on MongoDB -- no Mongo driver or running server required -- just a simple `yarn seed` to generate data in MongoDB Atlas. An aside, but it is a good idea to have two separate databases if opting into using a cloud service for local development


![MongoDB-Atlas-Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7y1uo0ufmoch0seftxcw.png)

Upon checking the `User` collection in the `dev` database, we can see the seeded data for our most recently created user having email `kattie.gislason@gmail.com` 


```yaml
    _id: 623d0997f9677901309ee6f8
    imageMeta:
        - id:"623d0997f9677901309ee700"
        - uploadedAt: 2022-03-25T00:15:19.475+00:00
        - fileLastModified: 2022-03-18T18:59:20.314+00:00
        - quality: 100
        - filename: "Katties-fresh-avatar"
        - src: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4apn65eo8acy..."
        - srcSet: ""
        - filetype: "GIF"
        - size: "0.25MB"
        - width: 125
        - height: 125
        - caption: "Voluptatem nisi hic beatae accusantium."
        - description: "Nihil vel aliquid dignissimos quam expedita. Dolore recusandae eum dig..."
        - title: "Kattie Gislason's Avatar"
        - ariaLabel: "Accessibility label"
        - destination: "AVATAR"
    name: "Kattie Gislason"
    email: "kattie.gislason@gmail.com"
    username: "Kattie_Gislason"
    image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4apn65eo8acy..."
    role: "USER"
    status: "OFFLINE"
    createdAt: 2022-03-25T00:15:19.476+00:00
    updatedAt: 2022-03-25T00:15:19.477+00:00
    email_verified: 2022-03-25T00:15:19.477+00:00
```

Nice. Now, back to React+JSX for the remainder of the article &rarr;

![nice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5i7z4bo2h2wa9s7lsm2a.gif) 
---


## Phase Two: Unwrapping a single JSX.IntrinsicElement

First, let's find the type definition for an `<input />` JSX.IntrinsicElement: 

```ts
declare global {
    namespace JSX {
        // some interesting generic usage happening here 

        interface IntrinsicElements {
            // other elements
            input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
            // more elements
        }
    }
}

```
`declare global { namespace JSX {} }` was intentionally included in the above typedefinition as it's important to think about where types are coming from, where they could go, and how we could use generics to achieve various desired outcomes. 

The first method I used when approaching this task was a localized, cut'n'dry, mimicry + mapping approach:

```ts
export type UnwrapInputProps<
  T extends keyof DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
  >
> = {
  [P in T]?: DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
  >[P];
};
```
In the above `UnwrapInputProps` type definition, T is extending the keyof an exact replica of the inner workings of the official input JSX.IntrinsicElement typedef we looked up at the start of this phase. `UnwrapInputProps<T>` is used for scaffolding custom input components in practice as follows:

```ts
export const InjectNameInput = ({
  ...props
}: UnwrapInputProps<
  | "className"
  | "type"
  | "name"
  | "autoComplete"
  | "id"
  | "required"
  | "value"
  | "onChange"
  | "placeholder"
>) => <input {...props} />;
```

Upon examining the intellisense it quickly becomes clear that...

![lacks-comprehensive-global-reuse-intuitive-nature](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a37nxb9xpb35xkq1dhr.png)

...this is hardly an optimized or ideal approach as far as widespread adoption is concerned. Why? It necessitates the manual entering of each desired prop-type which can be annoying to remember and tedious to maintain, especially with multiple devs in a single codebase.

Let's see how `InjectNameInput` is actually consumed

```tsx
            <InjectNameInput
              className={cn(
                `gform_${formIdRef.current}_gfield_nameinput_${
                  router.query.slug as string
                }`,
                placeholder.includes("Given Name")
                  ? "visible min-w-full"
                  : placeholder.includes("Surname")
                  ? "visible min-w-full"
                  : ""
              )}
              type='text'
              name={key}
              id={`input_${formIdRef.current}_${id}_${key}`}
              placeholder={placeholder}
              autoComplete={AUTOCOMPLETE_ATTRIBUTES[key]}
              value={nameValues?.[key] || ""}
              onChange={handleChange}
            />

```
Next, our final phase, phase tres. While there are other interesting intermediates on the way to the current working solution, heightened drowsiness and a desire to return to the code editor before sleep creeps in warrants getting to the point. 

If you'd like for me to update this post and expand on one or two additional intermediate solutions that build off of the previous approach and the pros/cons therein please drop a comment letting me know below!

![archer-booping-boopingly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o00varyeyy7h39n5kv4x.gif)

---

## Phase Three -- The Utility of .d.ts files

First, head to your `tsconfig.json` file to ensure that the following flag is set -- `"declaration": true`

The contents of my current tsconfig.json (as of 2022-03-24)
```json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "ES2020",
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "declaration": true,
    "strict": true,
    "pretty": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "skipDefaultLibCheck": true,
    "moduleResolution": "Node",
    "sourceMap": true,
    "strictBindCallApply": true,
    "noStrictGenericChecks": false,
    "strictFunctionTypes": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "jsx": "preserve",
    "downlevelIteration": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization": true,
    "baseUrl": "./",
    "allowJs": true,
    "sourceRoot": "./src",
    "checkJs": false,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "isolatedModules": true,
    "incremental": true,
    "paths": {
      "@/apollo/*": ["src/apollo/*"],
      "@/components/*": ["src/components/*"],
      "@/graphql/*": ["src/graphql/*"],
      "@/hooks/*": ["src/hooks/*"],
      "@/lib/*": ["src/lib/*"],
      "@/pages/*": ["src/pages/*"],
      "@/styles/*": ["src/styles/*"],
      "@/types/*": ["src/types/*"],
      "@/utils/*": ["src/utils/*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "index.d.ts",
    "graphqls.d.ts",
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.graphqls.d.ts",
    "src/**/*.graphql.d.ts",
    "src/**/*.graphqls",
    "src/**/*.graphql",
    "src/**/*.tsx",
    "src/**/*.js",
    "src/**/*.gql"
  ],
  "exclude": ["node_modules"]
}
```

Right, on to the good stuff. With the `declaration` flag set to true, create a root `index.d.ts` file. Please be sure to _"include"_ the file at the bottom of your tsconfig.json file within the `"include": []` array too (for TS to detect it).


#### index.d.ts
```ts
// Recursive Optional Mapping good-good
declare type RecursiveOptional<T> = {
  [P in keyof T]?: RecursiveOptional<T[P]>;
};

// Strip RecursiveOptional wrapper post-recursion for 1:1 alignment with core react typedefs
declare type OmitRecursiveOptionalWrapper<T> = T 
extends RecursiveOptional<
  infer U
>
  ? U
  : T;

// strips the recursively conditional helper type for 1:1 alignment with Reacts internal definitions
declare const ReactRecursiveUnwrapped = ({
  jsxProps
}: {
  jsxProps: Partial<
    OmitRecursiveOptionalWrapper<
      RecursiveOptional<
        JSX.IntrinsicElements
      >
    >
  >;
}) => ({ ...jsxProps });

// TypeDef to use Globally
declare type ReactUnwrapped<
  T extends keyof ReturnType<typeof ReactRecursiveUnwrapped>
> = {
  [P in T]?: ReturnType<typeof ReactRecursiveUnwrapped>[P];
};
```

Let's break this down:
- `OmitRecursiveOptionalWrapper` and `RecursiveOptional` are both helper types. `RecursiveOptional` conditionally maps all the props in `<T[P]>`. 
- The `[P in keyof T]?:` notation indicates that every property mapped is made conditional by the `?:` operator. If that were instead `[P in keyof T]-?:` then every property mapped would be stripped of its conditional status and made required. 
- If your aim is to avoid manipulating the required vs conditional status for any given mapped property, simply omit the question mark altogether `[P in keyof T]:`.

`OmitRecursiveOptionalWrapper` is the yin to `RecursiveOptional`'s yang in this context. How? Why? The Omitting Wrapper strips the transformed (conditionally mapped) output type of the `RecursiveOptional` typedef, which otherwise clashes with React's typedefs under the hood leading to errors. 

- The `OmitRecursiveOptionalWrapper<T>` type declaration may look familiar -- recall the config for the `UnwrapPromise<T>` type from Phase One:

```ts
declare type OmitRecursiveOptionalWrapper<T> = T 
extends RecursiveOptional<
  infer U
>
  ? U
  : T;
```

Breakdown of what's happening in the Omit Wrapper above:
- T extends any RecursiveOptional-Wrapped property U and infers its type
- if T does indeed extend or encounter such a configuration, it only returns the inner property U which consequently eliminates the outer RecursiveOptional type in the process
- else, if it does not encounter a RecursiveOptional wrapped type, it simply returns T

#### The Bread'n'Butter
If you've made it this far I extend my thanks, may the force of generics be with you. Now the good stuff -- let's examine the two remaining declarations in question. The first, `ReactRecursiveUnwrapped` is a const that returns a destructured/spread `jsxProps` of type `Partial<JSX.IntrinsicElements>`

```ts
// strips the recursively conditional helper type for 1:1 alignment with Reacts internal definitions
declare const ReactRecursiveUnwrapped = ({
  jsxProps
}: {
  jsxProps: Partial<
    OmitRecursiveOptionalWrapper<
      RecursiveOptional<
        JSX.IntrinsicElements
      >
    >
  >;
}) => ({ ...jsxProps });
```

Let's take a look at the intellisense for this typedef:

![react-recursive-unwrapped](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhiac4atuw8cu97h404g.png)

- But wait -- there are more typedefs assigned to the type of `jsxProps` initially...but also -- recall the yin/yang dynamic of the two helper types. `OmitRecursiveOptionalWrapper` wraps the `RecursiveOptional` wrapper to effectively cancel one another out _after_ the internal `JSX.IntrinsicElements` interface has already been recursively (and conditionally) mapped out by the `RecursiveOptional` wrapper! Leaving us with a much friendlier typedef to work with -- `Partial<JSX.IntrinsicElements>`

![woohoo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe5hwxt6ksrcv5mvp5a7.gif)

Lastly, let's examine the `ReactUnwrapped<T extends keyof ReturnType<typeof ReactRecursiveUnwrapped>>` type which we will use globally with _0 imports required_

```ts
declare type ReactUnwrapped<
  T extends keyof ReturnType<typeof ReactRecursiveUnwrapped>
> = {
  [P in T]?: ReturnType<typeof ReactRecursiveUnwrapped>[P];
};
```

- The intellisense for `T`, which extends `keyof ReturnType<typeof ReactRecursiveUnwrapped>` -- which is equivalent to `keyof ReturnType<Partial<JSX.IntrinsicElements>>`-- is as follows:

```ts
T extends "symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | ... 155 more ... | "view">
```

The `ReturnType<T>` for the declared const `ReactRecursiveUnwrapped` is equivalent to the definition of the JSX-namespace-residing `IntrinsicElements{}` interface

```ts
{
    a?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> | undefined;
    ... 173 more ...;
    view?: React.SVGProps<...> | undefined;
}
``` 
The only discernible difference? The recursive optional mapping, indicated by `[P in T]?:` within the `ReactUnwrapped` declaration, results in each JSX.IntrinsicElement having a conditionally undefined union type ` Type |undefined`

Ultimately, the globally utilized type has the following general shape:

```ts
type ReactUnwrapped<T extends "symbol" | "object" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | ... 155 more ... | "view"> = { [P in T]?: {
    a?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> | undefined;
    ... 173 more ...;
    view?: React.SVGProps<...> | undefined;
}[P] | undefined; }
```
Since these types are declared in a root `index.d.ts` file they are automatically available for global consumption with zero imports required.

To recap, these four declarations are of immediate utility for our purposes: 

```ts
declare type RecursiveOptional<T> = {
  [P in keyof T]?: RecursiveOptional<T[P]>;
};

declare type OmitRecursiveOptionalWrapper<T> = T 
extends RecursiveOptional<
  infer U
>
  ? U
  : T;

declare const ReactRecursiveUnwrapped = ({
  jsxProps
}: {
  jsxProps: Partial<
    OmitRecursiveOptionalWrapper<
      RecursiveOptional<
        JSX.IntrinsicElements
      >
    >
  >;
}) => ({ ...jsxProps });

declare type ReactUnwrapped<
  T extends keyof ReturnType<typeof ReactRecursiveUnwrapped>
> = {
  [P in T]?: ReturnType<typeof ReactRecursiveUnwrapped>[P];
};
```

---

## Consuming the `ReactUnwrapped` type in .tsx files
Vercel tends to use a top-level `Page` component to wrap an apps Layout with. This Page component is adapted from Vercel's new `@vercel/examples-ui` package, the [codebase for which can be found here](https://github.com/vercel/examples/tree/main/packages/ui)

Now let's inject a `<main />` JSX.IntrinsicElement with all of its native props to effectively make them available for consumption anytime the `Page` component is used elsewhere in your codebase as follows:

```tsx
export const Page: FC<ReactUnwrapped<"main">> = ({ children, ...props }) => (
  <main
    {...props.main}
    className={cn(
      "w-full max-w-3xl mx-auto py-16",
      props.main?.className ?? ""
    )}>
    {children}
  </main>
);
```

##### Noteworthy mention

Notice how `children` (aka `ReactNode`) is passed into props, provided by the outer React Functional Component type wrapper, `FC<ReactUnwrapped<"main">>`. `VFC`, or `Void Functional Component` has become increasingly popular over the past year, being cited as better practice than `FC` as it doesn't automatically inject ReactNode (children) on each and every use. But what about the children being passed in to this globally significant Page wrapper? That's where `ReactUnwrapped<T>` comes in! 

When using the `ReactUnwrapped<"main">` type, _all_ of the `<main />` Intrinsic Elements props are injected _including children_. The above Page component can be rewritten as follows:

```tsx
export const Page: VFC<ReactUnwrapped<"main">> = ({ ...props }) => (
  <main
    {...props.main}
    className={cn(
      "w-full max-w-3xl mx-auto py-16",
      props.main?.className ?? ""
    )}>
    {props.main?.children ?? <></>}
  </main>
);
```
This works for `<div />`, `<svg />`, `<p />`, `<span />`, or just about every other Intrinsic Element. With the global `ReactUnwrapped<T>` helper you can repurpose its intrinsic `children` prop to wherever is deemed most suitable.

##### A GitHub SVG

- Example of injecting and using a GitHub Icon:

```tsx
import type { VFC } from "react";

const GitHubIcon: VFC<ReactUnwrapped<"svg" | "path">> = ({ svg, path }) => (
  <svg
    {...svg}
    className={svg?.className ? svg.className : "h-6 w-6"}
    xmlns='http://www.w3.org/2000/svg'
    fill={svg?.fill ? svg.fill : "none"}
    viewBox='0 0 24 24'
    stroke={svg?.stroke ? svg.stroke : "currentColor"}>
    <path
      {...path}
      d='M12 0C5.374 0 0 5.373 0 12C0 17.302 3.438 21.8 8.207 23.387C8.806 23.498 9 23.126 9 22.81V20.576C5.662 21.302 4.967 19.16 4.967 19.16C4.421 17.773 3.634 17.404 3.634 17.404C2.545 16.659 3.717 16.675 3.717 16.675C4.922 16.759 5.556 17.912 5.556 17.912C6.626 19.746 8.363 19.216 9.048 18.909C9.155 18.134 9.466 17.604 9.81 17.305C7.145 17 4.343 15.971 4.343 11.374C4.343 10.063 4.812 8.993 5.579 8.153C5.455 7.85 5.044 6.629 5.696 4.977C5.696 4.977 6.704 4.655 8.997 6.207C9.954 5.941 10.98 5.808 12 5.803C13.02 5.808 14.047 5.941 15.006 6.207C17.297 4.655 18.303 4.977 18.303 4.977C18.956 6.63 18.545 7.851 18.421 8.153C19.191 8.993 19.656 10.064 19.656 11.374C19.656 15.983 16.849 16.998 14.177 17.295C14.607 17.667 15 18.397 15 19.517V22.81C15 23.129 15.192 23.504 15.801 23.386C20.566 21.797 24 17.3 24 12C24 5.373 18.627 0 12 0Z'
      fill={path?.fill ? path.fill : "currentColor"}
    />
    {svg?.children ? svg.children : <></>}
  </svg>
);

export default GitHubIcon;
```
###### Consuming
```tsx
export const CustomDiv = ({ div }: ReactUnwrapped<"div">) => (
  <div {...div}>
    <GitHubIcon
      svg={{
        "aria-hidden": true,
        "aria-label": "GitHubIcon",
        onChange: e => {
          e.preventDefault();
          e.currentTarget.style.cssText.replace(
            "GitHubIcon",
            "Changing Text Underway"
          );
        }
      }}
      path={{
        name: "GitHubIconPath",
        onMouseOver: e => {
          e.preventDefault();
          // do things
        }
      }}
    />
    {div?.children}
  </div>
);
```

That's all for now, I'll check back in regularly to answer questions/update and polish this post. Thanks for reading along! You can check out the github repo that the bulk of this code originates from [here](https://github.com/DopamineDriven/nextjs-headless-wp)

--- 

PS -- Twice a day🎉 

![commit-twice-daily](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nzwadhsyk0g4nfyd05y.gif)