Preprocessing slices to wrap a certain types of them in a container

Hello. I am trying to solve a challenge which should be fairly simple, but without full success yet.

My goal is to wrap a certain types of slices in a container. I need to preprocess the slices and create a wrapper div for slices of type "content_block" which comes one after another.

The outcome I want is below. What is the best way to approach it in Next.js with Typescript?

<main>
  <SliceA>
  <SliceB>
  <div class="wrapper">
    <ContentBlockSlice>
    <ContentBlockSlice>
  </div>
  <SliceC>
  ... 
  <div class="wrapper">
    <ContentBlockSlice>
    <ContentBlockSlice>
  </div>
</main>

I want to do it in page.tsx
This is what I tried. This code kinda works, but I can't get rid of Typescript issues...

const Component = components[slice.slice_type];
return Component ? <Component key={index} slice={slice} /> : null;

With this part in particular. "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type". How can I fix this line?

Full code below.

const processedSlices = [];
  let contentBlockGroup: ContentBlockSlice[] = [];

  page.data.slices.forEach((slice, index) => {
    if (slice.slice_type === 'content_block') {
      contentBlockGroup.push(slice);
    } else {
      if (contentBlockGroup.length) {
        processedSlices.push({
          slice_type: 'content_block_group',
          items: contentBlockGroup,
        });
        contentBlockGroup = [];
      }
      processedSlices.push(slice);
    }
  });

  if (contentBlockGroup.length) {
    processedSlices.push({
      slice_type: 'content_block_group',
      items: contentBlockGroup,
    });
  }

return (
    <Main>
      {processedSlices.map((slice, index) => {
        if (slice.slice_type === 'content_block_group') {
          return (
            <div key={index} className='wrapper'>
              {slice.items.map((contentBlockSlice, cbIndex) => (
                <ContentBlock
                  index={cbIndex}
                  key={cbIndex}
                  slices={slice.items}
                  slice={contentBlockSlice}
                  context={null}
                />
              ))}
            </div>
          );
        } else {
          const Component = components[slice.slice_type];
          return Component ? <Component key={index} slice={slice} /> : null;
        }
      })}
    </Main>
  );

@kodo

Seeing the TS error might help me frame my response a little better. However, I'll take a shot here.

Let me think through your code:

const processedSlices = [];
// declare an empty constant array to hold slices  
let contentBlockGroup: ContentBlockSlice[] = [];
// declare another empty array to hold ContentBlockSlices

// loop through each slice
// is index needed here?
  page.data.slices.forEach((slice, index) => {
   // entering the loop
    // check slice_type
   // if it's 'content_block' add it to contentBlockGroup
    if (slice.slice_type === 'content_block') {
      contentBlockGroup.push(slice);
    } else {
    // if it's not content_block
    // check if contentBlockGroup's length is truthy
      if (contentBlockGroup.length) {
      // there's something in contentBlockGroup
      // so let's add it to processedSlices
      // it looks like processedSlices is meant to hold an object
      // rather than slices
        processedSlices.push({
          slice_type: 'content_block_group',
          items: contentBlockGroup,
        });
       // object has been added to processedSlices
       // reset contentBlockGroup to an empty array
        contentBlockGroup = [];
      }
     // if slice_type wasn't content_block 
     // and contentBlockGroup's length is falsey
     // add the slice object to processedSlices
     // (different than the other object pushed above)
      processedSlices.push(slice);
    }
  });

    // this next bit seems like a duplicate block
    // perhaps this isn't needed?
  if (contentBlockGroup.length) {
    processedSlices.push({
      slice_type: 'content_block_group',
      items: contentBlockGroup,
    });
  }

/*
* Thoughts. Type processedSlices. Let TS know what it will/could hold
* const processed: Array<ContentBlockSlice | all your other slice types | {slice_type: string, items: ContentBlockSlice[]}> = []
* I'm certain my TS noobness should use a generic in the example above
*/

return (
    <Main>
      {processedSlices.map((slice, index) => {
        if (slice.slice_type === 'content_block_group') {
          return (
            <div key={index} className='wrapper'>
              {slice.items.map((contentBlockSlice, cbIndex) => (
                <ContentBlock
                  index={cbIndex}
                  key={cbIndex}
                  slices={slice.items}
                  slice={contentBlockSlice}
                  context={null}
                />
              ))}
            </div>
          );
        } else {
          const Component = components[slice.slice_type];
          return Component ? <Component key={index} slice={slice} /> : null;
        }
      })}
    </Main>
  );

I hope that helps some.

@nf_mastroianni Thanks for your reply.

the problem is with the below line of code:
const Component = components[slice.slice_type];

This is the full typescript message

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ articles: ComponentType; career_about: ComponentType; case_studies_latest: ComponentType<...>; ... 24 more ...; values: ComponentType<...>; }'.
No index signature with a parameter of type 'string' was found on type '{ articles: ComponentType; career_about: ComponentType; case_studies_latest: ComponentType<...>; ... 24 more ...; values: ComponentType<...>; }'.

Can you take a screenshot so I can see where TS underlines the element it doesn't like?

Also, can you console.log your slice object:

{processedSlices.map((slice, index) => {
        console.log('slice ===> ', slice)
        if (slice.slice_type === 'content_block_group') {
          return (
            <div key={index} className='wrapper'>
              {slice.items.map((contentBlockSlice, cbIndex) => (
                <ContentBlock
                  index={cbIndex}
                  key={cbIndex}
                  slices={slice.items}
                  slice={contentBlockSlice}
                  context={null}
                />
              ))}
            </div>
          );
        } else {
          const Component = components[slice.slice_type];
          return Component ? <Component key={index} slice={slice} /> : null;
        }
      })}
1 Like

Thanks @nf_mastroianni

The slice console.logs either the content block_group, or just a regular slice (screenshot 2)

Screenshot 2024-02-16 o 16.01.36

This is the complete code for my component where I try to preprocess the slides. And complete message from Typescript below.

'use client';

import { components } from '@/slices';

import {
  CaseStudyDocumentDataSlicesSlice,
  ContentBlockSlice,
} from '@/prismicio-types';
import ContentBlock from '@/slices/ContentBlock';

type StoryContentProps = {
  slices: CaseStudyDocumentDataSlicesSlice[];
  className?: string;
};

export default function StoryContent({
  slices,
  className,
  ...restProps
}: StoryContentProps) {
  const processedSlices = [];
  let contentBlockGroup: ContentBlockSlice[] = [];

  slices.forEach((slice, index) => {
    if (slice.slice_type === 'content_block') {
      contentBlockGroup.push(slice);
    } else {
      if (contentBlockGroup.length) {
        processedSlices.push({
          slice_type: 'content_block_group',
          items: contentBlockGroup,
        });
        contentBlockGroup = [];
      }
      processedSlices.push(slice);
    }
  });

  if (contentBlockGroup.length) {
    processedSlices.push({
      slice_type: 'content_block_group',
      items: contentBlockGroup,
    });
  }
  return (
    <>
      {processedSlices.map((slice, index) => {
        if (slice.slice_type === 'content_block_group') {
          return (
            <div key={index} className='wrapper' data-section-wrapper>
              <div className='navigator'></div>
              <div className='container'>
                {slice.items.map((contentBlockSlice, cbIndex) => {
                  return (
                    <ContentBlock
                      index={cbIndex}
                      key={cbIndex}
                      slices={slice.items}
                      slice={contentBlockSlice}
                      context={null}
                    />
                  );
                })}
              </div>
            </div>
          );
        } else {
          const Component = components[slice.slice_type];
          return Component ? <Component key={index} slice={slice} /> : null;
        }
      })}
    </>
  );
}

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ articles: ComponentType; career_about: ComponentType; case_studies_latest: ComponentType<...>; ... 24 more ...; values: ComponentType<...>; }'.
No index signature with a parameter of type 'string' was found on type '{ articles: ComponentType; career_about: ComponentType; case_studies_latest: ComponentType<...>; ... 24 more ...; values: ComponentType<...>; }'.

  const Component = components[slice.slice_type];

This Typescript message is referring to this single line.

Hi @kodo

I don't think I have a good answer, but here's something I was able to "figure out."

I was able to get the TS error to go away but recasting the type of slice. There is probably a good way to do this, but I certainly don't know it. However, to replicate this, I made a slice object and used a Hero slice as an example. If this was "the solution," you'd have to constantly update this to check for all your slice types. Not ideal by a longshot.

const slice = {
    slice_type: 'hero'
  } as unknown as HeroSlice | ContentSlice // etc
const Component = components[slice.slice_type]

For "hahas" you could check it out and see if it helps.