decoupled https://www.jamesdflynn.com/ en Using Drupal Blocks in a Decoupled GatsbyJS Application https://www.jamesdflynn.com/development/using-drupal-blocks-decoupled-gatsbyjs-application <span class="field field--name-title field--type-string field--label-hidden">Using Drupal Blocks in a Decoupled GatsbyJS Application</span> <span class="field field--name-uid field--type-entity-reference field--label-hidden"><span lang="" about="/user/1" typeof="schema:Person" property="schema:name" datatype="">jflynn</span></span> <span class="field field--name-created field--type-created field--label-hidden">Sun, 11/03/2019 - 12:40</span> <div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>One of the most useful components of Drupal is the <a href="https://www.drupal.org/docs/8/core/modules/block">Block system</a>. This system allows for pieces of content to be created and reused throughout a site in various regions of the page structure. You can also select which bundles or content types this piece of content should appear on, making it so that a blog post gets a certain Call to Action section while an article gets something similar, but different.</p> <p>When we use GatsbyJS for a decoupled front-end solution with a Drupal back-end, we lose out on quite a bit, if not everything, that the theme layer of Drupal provides. This means that Drupal regions mean nothing to Gatsby, nor does block placement. In fact, any functionality that would go into a <code>.theme</code> file is no longer available on the Drupal side.</p> <p>Before I get into the "how" of using Drupal blocks in Gatsby, I want to cover a little bit of the "why".</p> <h2>Why Use Drupal Blocks for Content in a Gatsby Front End?</h2> <p>The main advantage of blocks in Drupal is that the content is created in one place, but can be placed in several spots. I have worked with solutions that use Paragraphs (from the Paragraphs contrib module) for similar functionality, but the problem remains where the content needs to be recreated on every new parent entity. For example, I can create a Call to Action (CTA) Paragraph and place fields that reference them on every content type, but the Paragraphs themselves remain separate. A Paragraph is more like a collection of fields to be filled out than a reusable entity, and that's okay.</p> <p>In contrast, a Block can be created in one place, usually using the Block UI, and the content of the Block remains the same no matter where it is placed. A CTA block on a blog post will have identical content to the CTA block on an article. This makes content changes extremely fast compared to having to update every article and blog post if the wording or link need to change.</p> <p>It is entirely possible to create these types of entities in Gatsby by defining a reusable component, however the editing experience doesn't really pan out. It may require a developer to go in and edit a React component, adding a middle-person to the process. Using reusable Drupal blocks can save time and budget when set up appropriately.</p> <h2>How to Use Drupal Blocks for Content in a Gatsby Front End</h2> <p>This section is going to make a few assumptions.</p> <ol><li>You have a basic understanding of Gatsby and React components.</li> <li>You understand the Drupal theme layer.</li> <li>You have a basic understanding of the Drupal block system.</li> <li>Your decoupled application is already setup and pulling content from Drupal (not necessary, but it helps if you can try it out)</li> <li>You have the JSON:API module enabled on Drupal</li> <li>You're sourcing content from Drupal.</li> </ol><p>If you're having problems with any of these, feel free to reach out to me on Drupal Slack, I go by Dorf there.</p> <p>Now the "how".  We're going to start by creating a Custom Block Type. Let's keep going with the CTA theme and call it "CTA Block". We do this by logging into our site as someone with permissions to access the Block UI and going to <code>admin/structure/block/block-content/types.</code> Once there, select "Add custom block type".</p> <p>Let's label this CTA Block and begin creation. After we create it, we need to add some fields, so let's add the following fields:</p> <ul><li><strong>CTA Heading</strong> <ul><li>A plain text field, max 255 chars</li> </ul></li> <li><strong>CTA Link</strong> <ul><li>A Link field using URL and label, internal and external links, Label required.</li> </ul></li> <li><strong>CTA Content Types</strong> <ul><li>This is the field that will make all the difference. Create this field as Reference: Other... to begin with.</li> <li>Label it CTA Content Type.</li> <li>Under "Type of entity to reference" choose "Content type" from the select list, under Configuration.</li> <li>Set unlimited cardinality.</li> <li>Go through the remaining screens to create this field. Once you're back on the field list, select "Manage form display"</li> <li>From here, we're going to change the widget from Autocomplete to Check boxes/radio buttons.</li> <li>Save your changes</li> </ul></li> </ul><p>Now we're going to create a new block using this Custom Block Type. When we create the block under <code>block/add/cta_block</code> the form should look something like this:</p> <div alt="Screenshot of Block form" data-embed-button="media" data-entity-embed-display="media_image" data-entity-embed-display-settings="crop_freeform" data-entity-type="media" data-entity-uuid="016cec0b-963a-4042-a527-566d7f30d5d5" data-langcode="en" title="Block Form" class="embedded-entity"> <img src="/sites/default/files/styles/crop_freeform/public/Screen%20Shot%202019-11-03%20at%205.48.19%20PM_0.png?itok=ku5y9vEF" alt="Screenshot of Block form" title="Block Form" typeof="foaf:Image" class="image-style-crop-freeform" /></div> <p>Now, add whatever text you want to the fields, but only select a single content type in the CTA Content Type field.  Save the block and spin up your Gatsby dev environment. We're going to switch over there for a bit.</p> <p>Let's fire up our develop environment and take a look at GraphiQL to see what we have going on.</p> <div alt="GraphiQL query" data-embed-button="media" data-entity-embed-display="media_image" data-entity-type="media" data-entity-uuid="ea08daca-c426-4823-be5a-199d6ac056ba" data-langcode="en" title="GraphiQL" class="embedded-entity"> <img src="/sites/default/files/Screen%20Shot%202019-11-03%20at%206.54.59%20PM.png" alt="GraphiQL query" title="GraphiQL" typeof="foaf:Image" /></div> <p>As you can see, we now have access to <code>allBlockContentCtaBlock</code> in GraphiQL, but what are we going to do with it? Well, we are first going to create the CTA Block React component. We'll do that by creating the file <code>src/components/blocks/CtaBlock.js</code> and adding the following:</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' const CtaBlock = ({ data }) =&gt; { return &lt;div class='ctaBlock'&gt; &lt;h3&gt;CTA Heading&lt;/h3&gt; &lt;p&gt;CTA Text goes here and here and here.&lt;/p&gt; &lt;a href="http://example.com"&gt;Link Text&lt;/a&gt; &lt;/div&gt; } export default CtaBlock </code></pre> <p>This is pretty simple and doesn't include anything having to do with our GraphQL query yet, but we have the structure in place. Now, let's look at the data we can pull from GraphQL. We want to get the heading, body, link, and content type, so our query is going to look something like this:</p> <pre> <code class="language-json">query MyQuery { allBlockContentCtaBlock { nodes { field_cta_heading field_cta_link { title uri } body { value } relationships { field_cta_content_type { name } } } } } </code></pre> <p>Which will give us back:</p> <pre> <code class="language-json">{ "data": { "allBlockContentCtaBlock": { "nodes": [ { "field_cta_heading": "Heading for the CTA Block", "field_cta_link": { "title": "Learn more!", "uri": "http://google.com" }, "body": { "value": "&lt;p&gt;Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum facilisis, purus nec pulvinar iaculis, ligula mi congue nunc, vitae euismod ligula urna in dolor. Aenean massa.&lt;/p&gt;\r\n\r\n&lt;p&gt;Praesent nec nisl a purus blandit viverra. Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. Phasellus dolor.&lt;/p&gt;\r\n" }, "relationships": { "field_cta_content_type": [ { "name": "Blog Post" } ] } } ] } } }</code></pre> <p>Perfect! Now I want you to notice a couple of things here. First, we're querying ALL of the CTA blocks here. Second, we're using the content type in the query. Let's get this into our component. Add in the query to our CtaBlock.</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' const CtaBlock = ({ data }) =&gt; { return &lt;div class='ctaBlock'&gt; &lt;h3&gt;CTA Heading&lt;/h3&gt; &lt;p&gt;CTA Text goes here and here and here.&lt;/p&gt; &lt;a href="http://example.com"&gt;Link Text&lt;/a&gt; &lt;/div&gt; } export default CtaBlock export const CtaBlockQuery = graphql` allBlockContentCtaBlock { nodes { field_cta_heading field_cta_link { title uri } body { value } relationships { field_cta_content_type { name } } } }`</code></pre> <p>But wait, this isn't a page template, and it shouldn't be. What's the problem then? We have a query in a non-page template, and that's not good. What we need to do is figure out which of our templates are going to use this block and add it in there. Since we chose to have the block show on the blog post content type, we're going to use the blog post template. This will probably differ for your setup.</p> <p>Let's open up our page template and take a look:</p> <pre> <code class="language-javascript"> import React from 'react' import { graphql } from 'gatsby' import Layout from '../layouts' const BlogPostTemplate = ({ data }) =&gt; { const blogBody = data.nodeBlogPost.body.value return &lt;Layout&gt; &lt;h3&gt;{data.nodeBlogPost.title}&lt;/h3&gt; {data.nodeBlogPost.body.value} &lt;/Layout&gt; } export default BlogPostTemplate export const query = graphql` query($BlogPostID: String!){ nodeBlogPost(id: { eq: $BlogPostID }) { id title body { processed } } } ` </code></pre> <p>Now let's update this to have the block appear.</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' import Layout from '../layouts' import CtaBlock from '../blocks/CtaBlock' const BlogPostTemplate = ({ data }) =&gt; { const blogBody = data.nodeBlogPost.body.value return &lt;Layout&gt; &lt;h3&gt;{data.nodeBlogPost.title}&lt;/h3&gt; {data.nodeBlogPost.body.value} &lt;CtaBlock /&gt; &lt;/Layout&gt; } export default BlogPostTemplate export const query = graphql` query($BlogPostID: String!){ nodeBlogPost(id: { eq: $BlogPostID }) { id title body { processed } } } ` </code></pre> <p>But we still need to include the query for the block itself. To do this, we're going to make a few edits to both of our components. We'll start with the CtaBlock component and convert the query into a fragment that we can reuse in multiple places if need be.</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' const CtaBlock = ({ data }) =&gt; { return &lt;div class='ctaBlock'&gt; &lt;h3&gt;CTA Heading&lt;/h3&gt; &lt;p&gt;CTA Text goes here and here and here.&lt;/p&gt; &lt;a href="http://example.com"&gt;Link Text&lt;/a&gt; &lt;/div&gt; } export default CtaBlock export const CtaBlockQuery = graphql` fragment CtaBlockQuery on block_content__cta_block { field_cta_heading field_cta_link { title uri } body { value } relationships { field_cta_content_type { name } } }`</code></pre> <p>If you have a keen eye, you'll notice that we've removed the nodes section of the query. This is because we're now going to query for a single block. However, there is some risk to this. In the event that a content creator creates a new CTA block on Drupal for this content type instead of editing the existing one, the old one will remain in place because a single item query will only return a single item.</p> <p>Now, let's move back over to our page template and use this fragment to query for our block.</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' import Layout from '../layouts' import CtaBlock from '../blocks/CtaBlock' const BlogPostTemplate = ({ data }) =&gt; { const blogBody = data.nodeBlogPost.body.value return &lt;Layout&gt; &lt;h3&gt;{data.nodeBlogPost.title}&lt;/h3&gt; {data.nodeBlogPost.body.value} &lt;CtaBlock /&gt; &lt;/Layout&gt; } export default BlogPostTemplate export const query = graphql` query($BlogPostID: String!){ nodeBlogPost(id: { eq: $BlogPostID }) { id title body { processed } } blockContentCtaBlock (relationships: {field_cta_content_types: {elemMatch: {name: {eq: "Blog Post"} } } } ){ ...CtaBlockQuery } } `</code></pre> <p>Take a look at what we've done here.  We've added in a query for the CtaBlock and we're filtering it by the content type it's attached to. After that, we're pulling in everything from the query on our component. This is exactly what we were wanting to do, but there's another step that we need to take to actually use the data on our component.</p> <p>If you look at the JSX element for</p> <pre> <code>&lt;CtaBlock /&gt;</code></pre> <p><br /> you'll notice we aren't passing anything to it, so we've got to make sure it has data to work with or we're going to end up rendering nothing.  Edit that line to be </p> <pre> <code>&lt;CtaBlock data={data} /&gt;</code></pre> <p>In case you're not familiar, this is a React concept known as passing props, or properties, to a child component. We're passing the data object that was returned from our GraphQL query to the CtaBlock component so that it can use the included data. Since this is just a demo, we're passing the entire thing along, but it's easy enough to only pass the relevant parts of the object.</p> <p>Now back in our CtaBlock component we can use the data to render out our block's content.</p> <pre> <code class="language-javascript">import React from 'react' import { graphql } from 'gatsby' const CtaBlock = ({ data }) =&gt; { return &lt;div class='ctaBlock'&gt; &lt;h3&gt;{data.blockContentCtaBlock.field_cta_heading}&lt;/h3&gt; &lt;p dangerouslySetInnerHtml= {{ __html: data.blockContentCtaBlock.body.value}} /&gt; &lt;a href={data.blockContentCtaBlock.field_cta_link.uri}&gt;{data.blockContentCtaBlock.field_cta_link.title}&lt;/a&gt; &lt;/div&gt; } export default CtaBlock export const CtaBlockQuery = graphql` fragment CtaBlockQuery on block_content__cta_block { field_cta_heading field_cta_link { title uri } body { value } relationships { field_cta_content_type { name } } }`</code></pre> <p>Now we have our block based on content type rendering within our content type's Gatsby template. Note that I've left out a few things that should be noted for decoupled sites. </p> <ol><li>An internal link should us the Gatsby &lt;Link /&gt; component.</li> <li>Drupal needs some love to pass the correct alias over for an internal link so that it renders correctly.</li> <li>This is a very basic example. YMMV.</li> </ol><p>Anyway, I hope that this helps someone out there who ran into the same problems that I did. Please feel free to reach out to me on Twitter @jddoesdev, on Slack where I'm usually Dorf, or just leave a comment here if you have questions, concerns, comments, or just something nice to say.</p> <p>Also, please feel free to support my efforts in speaking on mental health in tech or creating blog posts and tutorials like these by checking out my gofundme and patreon campaigns in the sidebar.</p> <p>Thanks for reading!</p> </div> <div class="field field--name-field-category field--type-entity-reference field--label-above"> <div class="field__label">Category</div> <div class="field__item"><a href="/development" hreflang="en">Development</a></div> </div> <div class="field field--name-field-tags field--type-entity-reference field--label-above"> <div class="field__label">Tags</div> <div class="field__items"> <div class="field__item"><a href="/taxonomy/term/12" hreflang="en">Drupal Planet</a></div> <div class="field__item"><a href="/taxonomy/term/11" hreflang="en">Drupal</a></div> <div class="field__item"><a href="/taxonomy/term/21" hreflang="en">GatsbyJS</a></div> <div class="field__item"><a href="/taxonomy/term/22" hreflang="en">decoupled</a></div> <div class="field__item"><a href="/taxonomy/term/23" hreflang="en">JSON:API</a></div> </div> </div> <div class="field field--name-field-comments field--type-disqus-comment field--label-visually_hidden"> <div class="field__label visually-hidden">Comments</div> <div class="field__item"><drupal-render-placeholder callback="Drupal\disqus\Element\Disqus::displayDisqusComments" arguments="0=Using%20Drupal%20Blocks%20in%20a%20Decoupled%20GatsbyJS%20Application&amp;1=https%3A//www.jamesdflynn.com/development/using-drupal-blocks-decoupled-gatsbyjs-application&amp;2=node/23" token="F4UWrvkGA1S8tEBNlmh0l57oSf3DB-xuciA0qzTm1HE"></drupal-render-placeholder></div> </div> <div class="field field--name-field-header-image-entity field--type-entity-reference field--label-hidden field__item"><article class="media media--type-image media--view-mode-embedded"> <div class="field field--name-image field--type-image field--label-hidden field__item"> <img src="/sites/default/files/negative-space-compiled-code-screen.jpg" width="1200" height="800" alt="Generic &quot;Look at my screen full of code&quot; screen" loading="lazy" typeof="foaf:Image" /> </div> </article> </div> Sun, 03 Nov 2019 18:40:45 +0000 jflynn 23 at https://www.jamesdflynn.com