1

so I was trying to learn more about Gutenberg Blocks and I wanted to make a block that shows the last three blog posts - something that could be used at the bottom of each page of my Wordpress site. I looked at some tutorials and I was able to create a working version... well, it works in the editor (backend) but when I apply code to the save file (frontend) my code crashes the site and nothing is displayed.

I produced the following code for my block that queries the Wordpress database and retrieves the last three blog posts

import { useBlockProps } from '@wordpress/block-editor';
import apiFetch from '@wordpress/api-fetch';
import { useState, useEffect, useRef } from '@wordpress/element';
import { addQueryArgs } from '@wordpress/url';
import './editor.scss';

export default function Edit() {

    // are we loading the posts?
    const [ isLoading, setLoading ] = useState( true );
    // the posts we're displaying
    const [ posts, setPosts ] = useState([]);
    // we don't want to update state on an unmounted component
    const isStillMounted = useRef();

    // fetch the posts
    useEffect(() => {
        isStillMounted.current = true;
        apiFetch( {
            path: addQueryArgs( `/wp/v2/posts?_embed&filter[posts_per_page]=3`, { } ),
        } )
            .then( ( data ) => {
                if ( isStillMounted.current ) {
                    setPosts( data );
                    setLoading( false );
                }
            } )
            .catch( () => {
                if ( isStillMounted.current ) {
                    setPosts( [] );
                    setLoading( false );
                }
            } );
    }, [setPosts, setLoading, isStillMounted]);

    return (
        <div { ...useBlockProps() }>
            <div className="blog">
                <div className="blog__wrapper">
                    <div className="blog__header">
                        <h2 className="blog__header__heading">Blog Posts</h2>
                    </div>
                    <div className="blog__row">
                        {
                            posts.map((post) =>
                                <div className="blog__card">
                                    <div className="blog__card__details">
                                        <div className="blog__card__details__header">
                                            { post.title.rendered }
                                        </div>
                                        <div className="blog__card__details__teaser"
                                                 dangerouslySetInnerHTML={{__html: post.excerpt.rendered }}>
                                        </div>
                                        <div className="blog__card__details__date">
                                            { new Date(post.date).toLocaleString('en-GB', { timeZone: 'UTC' }).split(',')[0] }
                                        </div>
                                        <div className="blog__card__details__control">
                                            <a href={post.link} className="button-link">weiter lesen</a>
                                        </div>
                                    </div>
                                    <div className="blog__card__img" style={{ 'background-image': "url(" + post._embedded['wp:featuredmedia']['0'].source_url + ")"}}>
                                    </div>
                                </div>
                            )
                        }
                    </div>
                </div>
            </div>
        </div>
    );
}

In the editor this looks great! So I thought, as I don't actually do anything with the block (meaning the user doesn't add text, photos or anything) surely I can just apply the same code to the save.js file. However, just adding the js code from the edit file to the save file breaks everything. The console tells me that I can't use react hooks?

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

Here is the code in the save.js, I've just copied and pasted...

export default function save() {
// are we loading the posts?
    const [ isLoading, setLoading ] = useState( true );
    // the posts we're displaying
    const [ posts, setPosts ] = useState([]);
    // we don't want to update state on an unmounted component
    const isStillMounted = useRef();

    // fetch the posts
    useEffect(() => {
        isStillMounted.current = true;
        apiFetch( {
            path: addQueryArgs( `/wp/v2/posts?_embed&filter[posts_per_page]=3`, { } ),
        } )
            .then( ( data ) => {
                if ( isStillMounted.current ) {
                    setPosts( data );
                    setLoading( false );
                }
            } )
            .catch( () => {
                if ( isStillMounted.current ) {
                    setPosts( [] );
                    setLoading( false );
                }
            } );
    }, [setPosts, setLoading, isStillMounted]);


    return (
        <div { ...useBlockProps.save() }>
            I haven't added anything here...
        </div>
    );
}

So, I guess I can't really do this. Can I use react code in the Frontend? I am unsure if I am doing this correctly (is my approach wrong or just ode) or if what I want to do is achievable? I want to use a "static" block as I don't really know PHP. Any advice on how I should approach this problem or if you could share some links to produce such a block would be appreciated. I'm currently reading the article from the warning and looking at other posts here but any help you can give to educate me would be great. Thanks in advance.

1

1 Answer 1

3

That's not how blocks work, the save component generates static HTML, so you can't use interactive hooks or state.

The important part is that the save component only runs inside the editor on your browser. It does not run in the database, or on the client. You can't save a react component to the database and render it with interactivity on the frontend.

Instead if you want to fetch the 3 latest posts you have 2 options:

  • enqueue a JS file on the frontend that fetches the posts from the REST API
  • save null and use PHP filters to render the 3 latest posts

The latter is how core does it for full site editing blocks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.