Block Making

concepts for making blocks in this project

Demonstration

Block Making Demonstration

File Structure

This is the file structure from create-react-block. You can copy an existing block for convenience.

Register Block in index.tsx

import { registerBlockType } from "@wordpress/blocks";
import { __ } from "@wordpress/i18n";
import "./style.scss";
import edit from "./edit";
import save from "./save";
import { PREFIX } from "../../utils/config";

registerBlockType("gravity-platform-core/promotions", {
	title: __("Promotions", PREFIX),
	category: "gravity-platform",
	attributes: {
		formId: {
			type: "number",
			default: 1,
		},
		selectedPromotionIds: {
			type: "array",
			default: [],
		},
	},
	edit,
	save,
});

Any block attributes that you rely on to hydrate your components should be registered in index.tsx with their type and ideally a default value.

Create Custom Post Type - if needed

gravity/backend/plugins/gravity-platform-core/gravity-platform-core.php
function gravity_platform_core_blocks_init()
{
	register_block_type(__DIR__ . '/src/blocks/showroom');
  register_block_type(__DIR__ . '/src/blocks/promotions');

  register_post_type( 'promotions',
      array(
          'labels' => array(
              'name' => __( 'Promotions' ),
              'singular_name' => __( 'Promotion' )
          ),
          'public' => true,
          'has_archive' => true,
          'rewrite' => array('slug' => 'promotions'),
          'show_in_rest' => true,
          'show_in_graphql' => true,
          'hierarchical' => true,
          'graphql_single_name' => 'promotion',
          'graphql_plural_name' => 'promotions',
      )
  );
}
add_action('init', 'gravity_platform_core_blocks_init');

Build Your Query - if using a postType for your block

You can use the GraphQl IDE in the Wordpress Backend to build your query.

GraphQl IDE
gravity/frontend/lib/wp/queries/get-promotions.ts
import { gql } from "@apollo/client";

/**
 * GraphQL countries query.
 */
const GET_PROMOTIONS = gql`query GET_PROMOTIONS{
  promotions {
    nodes {
      title
      slug
      fields {
        description
        disclaimer
        endDate
        subtitle
        summary
        image {
          altText
          sourceUrl
        }
      }
    }
  }
}`;

export default GET_PROMOTIONS;

Make Your Frontend Block

frontend/components/blocks/promotions/PromotionsBlock.tsx
/**
 * Importing React is required for use in Gutenberg since WP Scripts doesn't compile components
 * as UMD modules (React is injected as a UMD module automatically in Next.js environments without an import)
 */
import React from "react";
import client from "../../../lib/wp/connector";
import GET_PROMOTIONS_QUERY from "../../../lib/wp/queries/get-promotions";
import Link from "next/link";

type Props = {
	promotions: any[];
};

export default function PromotionsBlock({ promotions }: Props) {
	return (
    <section className="text-gray-700 ">
      <div className="container items-center px-5 py-8 mx-auto lg:px-24">
        <div className="flex flex-wrap mb-12 text-left">
        {
          promotions.map( promotion => (
              <div className="w-full p-6 mx-auto lg:w-1/3">
                <div className="shadow-xl  rounded-xl bg-gray-50">
                  <img className="object-cover object-center w-full lg:h-48 md:h-36 rounded-t-xl" src={'http://' + promotion.fields.image.sourceUrl.substring(8)} alt="blog" />
                  <div className="p-4 lg:p-8 bg-gray-50">
                    <h1 className="mx-auto mb-4 text-2xl font-semibold leading-none tracking-tighter text-black lg:text-3xl title-font">{ promotion.title }</h1>
                    <h2 className="mb-8 text-xs font-semibold tracking-widest text-black uppercase title-font" dangerouslySetInnerHTML={{__html: promotion.fields.subtitle}}></h2>
                  </div>
                  <div className="px-6 py-4 bg-gray-100 rounded-b-xl">
                    <Link href={'promotions/' + promotion.slug}>
                      <a className="px-4 py-1 mr-1 text-base text-gray-500 transition duration-500 ease-in-out transform rounded-md focus:shadow-outline focus:outline-none focus:ring-2 ring-offset-current ring-offset-2 hover:text-black ">
                        Details »
                      </a>
                    </Link>
                    <a href="#" className="inline-flex items-center mt-auto font-semibold text-blue-600 lg:mb-0 hover:text-black " title="Read Full Article">  </a>
                  </div>
                </div>
              </div>
            )
          )
        }
        </div>
      </div>
    </section>
	);
}

const getPropsData = async () => {
	const { data } = await client.query({
		query: GET_PROMOTIONS_QUERY
	});

	return {
		props: {
			promotions: data?.promotions?.nodes ?? []
		}
	};
};

/**
 * Called from WP's block preview screen.
 * Separate from getServerSideProps because private/session based data might need to be mocked.
 * @returns
 */
export const getPreviewProps = async () => {
	return await getPropsData();
};

/**
 * Called by the page the block exists on within it's getServerSideProps method.
 * The result is passed to the component props.
 * @returns
 */
export const getServerSideProps = async () => {
	return await getPropsData();
};

When creating & registering your block the naming convention is to kabab-case rather than TitleCase or camelCase.

Detail Page with Dynamic Routes

gravity/frontend/pages/promotions/[slug].tsx
import { GetServerSideProps } from "next";
import { ReactElement } from "react";
import Layout from "../../components/Layout";
import client from "../../lib/wp/connector";
import { PROMOTION_BY_SLUG_QUERY } from "../../lib/wp/queries/promotion-by-slug";
import { getSiteByDomain } from "../../lib/sites";

type Props = {
	promotion: any;
};

export default function PromotionDetail({ promotion }: Props): ReactElement {
	return (
		<Layout title={promotion.title}>
      <section className="text-gray-600 body-font">
        <div className="container mx-auto flex px-5 py-24 md:flex-row flex-col items-center">
          <div className="lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center">
            <h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">{promotion.fields.subtitle}</h1>
            <p className="mb-8 leading-relaxed" dangerouslySetInnerHTML={{__html: promotion.fields.summary}}></p>
            <p className="text-sm mt-2 text-gray-500 mb-8 w-full" dangerouslySetInnerHTML={{__html: promotion.fields.description}}></p>
          </div>
          <div className="lg:max-w-lg lg:w-full md:w-1/2 w-5/6">
            <img className="object-cover object-center w-full lg:h-48 md:h-36 rounded-t-xl" src={'http://' + promotion.fields.image.sourceUrl.substring(8)} alt={promotion.title} />
          </div>
        </div>
      </section>
		</Layout>
	);
}

export const getServerSideProps: GetServerSideProps = async ({
	req,
	res,
  resolvedUrl
}) => {
	res.setHeader(
		"Cache-Control",
		"public, s-maxage=1, stale-while-revalidate=86399" // 24 hours
	);

	// allow self-signed certs when developing locally
	if (process.env.NODE_ENV === "development") {
		process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
	}

	const site = getSiteByDomain(req.headers.host);

	if (!site) {
		return {
			notFound: true
		};
	}

	const uri = resolvedUrl;

	const { data } = await client.query({
		query: PROMOTION_BY_SLUG_QUERY,
		variables: { uri }
	});

	return {
		props: {
			promotion: data?.promotion
		}
	};
};

Query Details for Single Item

/gravity/frontend/lib/wp/queries/promotion-by-slug.ts
import { gql } from "@apollo/client";

export const PROMOTION_BY_SLUG_QUERY = gql `
  query Promotion($uri: ID!) {
    promotion(id: $uri, idType: URI) {
      id
      slug
      title
      fields {
        description
        disclaimer
        endDate
        subtitle
        summary
        image {
          altText
          sourceUrl
        }
      }
    }
  }
`

Last updated

Was this helpful?