Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A helper to add/remove/edit data in a Slice Zone's Slices (mapSliceZone()) #303

Open
angeloashmore opened this issue May 31, 2023 · 2 comments
Labels
enhancement New feature or request

Comments

@angeloashmore
Copy link
Member

Is your feature request related to a problem? Please describe.

By default, Slice Zones only contain data provided directly from the Prismic API. This is sufficient in most cases, but extra data, or less data in some cases, is needed.

Developers can modify their Slice Zone data manually today using Array.prototype.map() and custom logic, but the solution is not straightforward. Anyone needing this functionality will need to implement it ad-hoc.

Describe the solution you'd like

We could provide a mapSliceZone() function that accepts a Slice Zone array and an object of functions that map a particular type of Slice to some other obhject. The function could add, edit, or remove data from a Slice.

The following example reshapes Code Block Slices (id: code_block) by replacing it with an object containing a single codeHTML property. The property contains HTML produced from a highlight() function, which could be a function that calls a syntax highlighting library, like Shiki.

import { mapSliceZone } from "@prismicio/client";

import { highlight } from "@/lib/highlight";

const mappedSliceZone = await mapSliceZone(page.data.slices, {
  code_block: async ({ slice }) => ({
    codeHTML: await highlight(slice.primary.code, slice.primary.language),
  }),
});

Any occurance of a Code Block Slice will be replaced with the mapped version. Note how the function provided to the code_block property receives the slice object. Other data is included, such as slices, and index.

A third parameter (context) can be provided to unstable_mapSliceZone(), whose contents will be passed to the function in the context property. The context object is most helpful when mapping functions are defined outside the unstable_mapSliceZone() calling scope, such as in a different module.

const mappedSliceZone = await mapSliceZone(
  page.data.slices,
  {
    code_block: async ({ slice, context }) => ({
      codeHTML: await highlight(slice.primary.code, slice.primary.language),
    }),
  },
  context
);

Describe alternatives you've considered

Developers can use Array.prototype.map() directly and implement custom logic. This helper would do exactly that, but with a framework around how to define that custom logic, along with thorough TypeScript types.

Additional context

This feature request has been prototyped internally.

@angeloashmore angeloashmore added the enhancement New feature or request label May 31, 2023
@angeloashmore angeloashmore changed the title A helper to add/remove/edit data in a Slice Zone's Slices A helper to add/remove/edit data in a Slice Zone's Slices (mapSliceZone()) May 31, 2023
@angeloashmore
Copy link
Member Author

An initial implementation is being developed in #302. It is being exposed as unstable_mapSliceZone(), notably with the unstable_ prefix as the implementation and name could change.

The following serves as documentation for unstable_mapSliceZone(). The guide is specific to Next.js, but could be generalized to any JavaScript project that can run async functions.


Things to know

  • It is not designed to work with Slice Simulator at this time. Slice Simulator needs to be modified to work with server-side data fetching before Slice mappers can be used.
  • The names “mapper” and “mapSliceToProps” are temporary.
  • Sync or async functions are supported.
  • Mapper functions can add data to a Slice, but also reduce data sent across the network. When trying this new functionality, try to trim down the data returned by getStaticProps(); do as much pre-processing on the server as possible.

How to use

1. Create a mapper

  1. In a Slice’s directory (e.g. src/slices/CallToAction), create a new file named mapper.ts.

  2. Copy the following contents into the file (a CallToAction Slice is used as an example):

    // src/slices/CallToAction/mapper.ts
    
    import { Content } from "@prismicio/client";
    
    import { Mapper } from "@/components/SliceZone";
    
    import { CallToActionProps } from ".";
    
    const mapSliceToProps: Mapper<
      Content.CallToActionSlice,
      CallToActionProps
    > = async () => {
      return {
        // Add your modified Slice props here.
      };
    };
    
    export default mapSliceToProps;

    Note that the mapSliceToProps() function is typed using Mapper. The Mapper type accepts two type parameters: the mapper’s Slice type and the Slice’s component props type.

  3. Add your custom props to mapSliceToProps()'s return value. This object will become your Slice’s props, replacing the default { slice, slices, index, context } props.

2. Create a mapper record

  1. In the Slice Library’s root directory, create a new file named mappers.ts adjacent to the Slice Machine-generated index.js file.

  2. Copy the following contents into the file (a CallToAction Slice is used as an example):

    import { Mappers } from "@prismicio/client";
    
    export const mappers = {
      call_to_action: () => import("./CallToAction/mapper"),
    } satisfies Mappers;
  3. Anytime a new mapper is created, add it to this record using the Slice API ID as a key and the lazy-loaded mapper function as a value. It will automatically be picked up by the mapSliceZone() function in getStaticProps().

3. Map Slices in getStaticProps()

  1. In your page’s getStaticProps() function, query your document.

  2. In a separate variable, use the mapSliceZone() function to trigger your mapper functions.

    import { GetStaticPropsContext } from "next";
    
    import { mapSliceZone } from "@/components/SliceZone";
    
    export async function getStaticProps({ previewData }: GetStaticPropsContext) {
      const client = createClient({ previewData });
    
      const page = await client.getByUID("page", "home");
      const slices = await mapSliceZone(page.data.slices, mappers);
    
      return {
        props: {
          slices,
        },
      };
    }
  3. In your page’s component, pass slices to <SliceZone>. The final page should look like this:

    import { GetStaticPropsContext, InferGetStaticPropsType } from "next";
    
    import { createClient } from "../../prismicio";
    import { components } from "@/slices";
    import { mappers } from "@/slices/mappers";
    
    import { mapSliceZone, SliceZone } from "@/components/SliceZone";
    
    type PageProps = InferGetStaticPropsType<typeof getStaticProps>;
    
    export default function Page({ slices }: PageProps) {
      return <SliceZone slices={slices} components={components} />;
    }
    
    export async function getStaticProps({ previewData }: GetStaticPropsContext) {
      const client = createClient({ previewData });
    
      const page = await client.getByUID("page", "home");
      const slices = await mapSliceZone(page.data.slices, mappers);
    
      return {
        props: {
          slices,
        },
      };
    }

Feedback

Please leave any feedback on the API, naming, or anything else in this issue. Thanks!

@angeloashmore
Copy link
Member Author

unstable_mapSliceZone() is available as of v7.1.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant