This guide presents an example Preact app that queries for block data and maps it into customized JavaScript components.
In order to run the Preact app below as a static .html
file in the browser, the Schema Configuration applied to the endpoint needs to have Response Headers with the following value:
Access-Control-Allow-Origin: null
Access-Control-Allow-Headers: content-type,content-length,accept
Copy
(The HTML code below is inspired by the Preact example in Automattic/vip-block-data-api
.)
The GraphQL query contained in the code below retrieves the post's block data as a JSON object (via field CustomPost.blockDataItems
), and then the JavaScript code maps each block data item into a custom component:
<! DOCTYPE html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title >Gato GraphQL - Mapping JS components to (Gutenberg) blocks: Preact example</ title >
</ head >
< body ></ body >
< script type = "module" >
import { h , render } from 'https://esm.sh/preact' ;
// Input here your domain, and enable the single endpoint
const endpoint = "https://mysite.com/graphql/" ;
// Input here the ID of a post with blocks
const postId = 40 ;
renderPost ( endpoint , postId );
async function renderPost ( endpoint , postId ) {
const data = {
query : `
query GetPost($postId: ID!) {
post(by: { id: $postId }) {
title
blockDataItems
}
}
` ,
variables : {
postId : ` ${ postId } `
},
};
const response = await fetch (
endpoint ,
{
method : 'post' ,
body : JSON . stringify ( data ),
headers : {
Accept : 'application/json' ,
'Content-Type' : 'application/json' ,
'Content-Length' : data . length ,
},
}
);
// Execute the query, and await the response
const json = await response . json ();
// If the query produced errors, exit
if ( json . errors ) {
console . log ( JSON . stringify ( json . errors ));
return ;
}
// Uncomment here to visualize the GraphQL response
// console.log(json.data);
const postTitle = json . data . post ?. title ;
const blocks = json . data . post ?. blockDataItems ;
const App = Post ( postTitle , blocks );
render ( App , document . body );
}
function mapBlockToComponent ( block ) {
if ( block . name === 'core/heading' ) {
return Heading ( block );
} else if ( block . name === 'core/paragraph' ) {
return Paragraph ( block );
} else if ( block . name === 'core/media-text' ) {
return MediaText ( block );
} else if ( block . name === 'core/gallery' ) {
return Gallery ( block );
} else if ( block . name === 'core/image' ) {
return Image ( block );
} else {
return null ;
}
}
/* Components */
function Post ( title , blocks ) {
return h ( 'div' , { className : 'post' },
h ( 'h1' , null , title ),
blocks . map ( mapBlockToComponent ),
);
}
function Heading ( props ) {
// Use dangerouslySetInnerHTML for rich text formatting
return h ( 'h2' , { dangerouslySetInnerHTML : { __html : props . attributes . content } });
}
function Paragraph ( props ) {
// Use dangerouslySetInnerHTML for rich text formatting
return h ( 'p' , { dangerouslySetInnerHTML : { __html : props . attributes . content } });
}
function MediaText ( props ) {
return h ( 'div' , { className : 'media-text' },
h ( 'div' , { className : 'media' },
h ( 'img' , { src : props . attributes . mediaUrl })
),
h ( 'div' , { className : 'text' },
props . innerBlocks ? props . innerBlocks . map ( mapBlockToComponent ) : null ,
),
)
}
function Gallery ( props ) {
return h ( 'div' , { className : 'gallery' },
h ( 'div' , { className : 'images' },
props . innerBlocks ? props . innerBlocks . map ( mapBlockToComponent ) : null ,
),
)
}
function Image ( props ) {
return h ( 'img' , { src : props . attributes . url });
}
</ script >
</ html >
Copy
Running the application produces the following HTML code from post data:
< div class = "post" >
< h1 >Welcome to a single post full of blocks!</ h1 >
< p >When I look back on my past and think how much time I wasted on nothing, how much time has been lost in futilities, errors, laziness, incapacity to live; how little I appreciated it, how many times I sinned against my heart and soul-then my heart bleeds. < strong >Life is a gift, life is happiness, every minute can be an eternity of happiness</ strong >. (< a href = "https://www.azquotes.com/author/4085-Fyodor_Dostoevsky" target = "_blank" rel = "noopener" >Quote by Fyodor Dostoevsky</ a >)< br ></ p >
< h2 >This blog post will be transformed...</ h2 >
< p >If you make it a habit not to blame others, you will feel the growth of the ability to love in your soul, and you will see the growth of goodness in your life. (< a href = "https://www.azquotes.com/author/14706-Leo_Tolstoy" target = "_blank" rel = "noopener" >Quote by Leo Tolstoy</ a >)< br ></ p >
< img src = "https://i.ytimg.com/vi/z4KKd2nGlEM/maxresdefault.jpg" >
< h2 >< mark style = "background-color:#D1D1E4" class = "has-inline-color" >I love these veggies!!!</ mark ></ h2 >
< h2 >When going to eat out, I normally go for one of these:</ h2 >
< h2 >This heading (H3) is boring (Regex test: $1 #1), but these guys are not</ h2 >
< div class = "gallery" >
< div class = "images" >
< img src = "https://i.insider.com/5f44388342f43f001ddfec52" >
< img src = "https://i.pinimg.com/originals/85/a7/f5/85a7f5cee4c93cb3995d1b51e3a0289f.jpg" >
</ div >
</ div >
</ div >
Copy