Hi, suppose I have block named "Container". I want to have an option to have "Container" block as a child of "Container" block (nest block into same block).
Is there an recommended approach on how to do that? Thanks
Because we can't do that:
export const Container: Block = {
slug: "container",
labels: {
singular: "Kontejner",
plural: "Kontejnery",
},
fields: [
{
label: "Komponenty",
labels: {
singular: "Komponenta",
plural: "Komponenty",
},
name: "layout",
type: "blocks",
required: true,
blocks: [
Container, // we can't use it before it's declared
],
},
],
};
I understand, we can do something like this to reuse fields and keep it a bit DRY and it would work, but isn't there really some better way?
const fields: Field[] = [
{
name: "width",
label: "Šířka kontejneru",
type: "radio",
options: [
{
label: "Výchozí šířka",
value: "default",
},
{
label: "Rozšířená šířka",
value: "extended",
},
{
label: "Plná šířka",
value: "full",
},
],
defaultValue: "default",
admin: {
layout: "horizontal",
},
},
{
name: "paddingY",
label: "Horizontální odsazení",
type: "radio",
options: [
{ label: "Malé", value: "sm" },
{ label: "Střední", value: "md" },
{ label: "Velké", value: "lg" },
{ label: "Velmi velké", value: "xl" },
],
defaultValue: "default",
admin: {
layout: "horizontal",
},
},
colorField,
];
export const Container: Block = {
slug: "container",
labels: {
singular: "Kontejner",
plural: "Kontejnery",
},
fields: [
...fields,
{
label: "Komponenty",
labels: {
singular: "Komponenta",
plural: "Komponenty",
},
name: "layout",
type: "blocks",
required: true,
blocks: [
Articles,
Button,
Columns,
ContactForm,
ContactSection,
Divider,
Faq,
Features,
Gallery,
Heading,
NumberedList,
OwlCarousel,
Content,
SectionImage,
Timeline,
{
slug: "container",
labels: {
singular: "Kontejner",
plural: "Kontejnery",
},
fields: [...fields],
},
],
},
],
};
Hi @Marťafiixek - apologies for the delay, is this the method you've settled on? Reusing your fields as you have done above is a nice solution honestly although it may feel bulky
Hi, yes - I settled on this method. Nesting containers works nicely with the Payload UI/UX and I can render the nested containers on the website with no problems. One minor thing I encountered today is this:
When you want to use types for blocks in your frontend code, you can do this:
type ButtonBlock = Extract<Page['layout'][0], { blockType: 'button' }>
The above works. However, how can I use this approach to get type of a block with
blockType === "articles"
that is nested one level deeper?
export interface Page {
id: string;
title: string;
layout: (
| {
blockType: 'container';
layout: (
| {
blockType: 'articles';
}
| {
blockType: 'button';
}
...
Eg. block "articles" nested in "container" block? I mean, I tried every possible solution but every time I got "never" as a type 😦
hmm, it's a good question
we have a roadmap item that will allow you to declare top-level interfaces for fields / blocks / etc - and then have those interfaces be re-used
which is likely what will solve this in a permanent way for you as it is
but in the interim, i feel like there should be a way to do this
especially if you can first get the type of your
ButtonBlock
, and then use
Extract
against
thattype in the same way you did to get the
ButtonBlock
itself... in theory, it's no different, right?
Thought the same thing, it shouldn't be different, however, somehow I can't make it work. When I try to do it like you suggest, I get
never
type, meaning the Extract utility doesn't "see" the
blockType === "articles"
So, I had to skip this and use an
any
type on all blocks that are deeply nested
And I will try to make it work after I deliver the project to client
I figured it out, gonna post a solution soon
So, let's imagine this tree:
- Container block
- Columns block
- Column block
The correct way to get the type of a column block is like this:
type ColumnBlock = Extract<
Extract<
Extract<Page["layout"][number], { blockType: "container" }>["layout"][number],
{ blockType: "columns" }
>["layout"][number],
{ blockType: "column" }
>;
Usage inside TSX below:
import { WIDTH } from "./style";
import { Page } from "payload-types";
import { joinClassNames } from "utils/helper";
import { RenderBlocks } from "components/RenderBlocks";
type ColumnBlock = Extract<
Extract<
Extract<Page["layout"][number], { blockType: "container" }>["layout"][number],
{ blockType: "columns" }
>["layout"][number],
{ blockType: "column" }
>;
const Column = ({ width, layout }: ColumnBlock) => {
return (
<div className={joinClassNames(WIDTH[width])}>
<RenderBlocks blocks={layout} />
</div>
);
};
export default Column;
Is it ugly? A bit. Does it work? Hell YEAH!
Also, I sometimes tend to omit properties like
blockType
that are needed by Payload but not needed by the FE component itself.
An example from a
Button
component:
type ButtonBlockType = Partial<
Omit<Extract<Page["layout"][0], { blockType: "button" }>, "blockType" | "link">
> & {
link?: any;
};
In the example above I also substituted the link property as I didn't want to think a lot about
link.reference.value
being string vs. the populated page or post (hope you understand what I mean) so I just set it as any (I know, demonic thing to do)
But yeah, top level interface or type for a block would be nice thing to have ❤️
amazing
this is a great interim solution for sure
but yes, shortly, we will have the ability to define to-level interfaces for sure
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.