1

I've been banging my head on this for a bit now.

I'm trying to create a simple wordpress block that fetches custom post types from the rest api, then displays the fetched posts via react. It only needs to do this, and requires no other editor functionality.

However, I'm a bit of a react/wp noob and I may be fundamentally misunderstanding how wordpress react functions, as I can't seem to get anywhere.

Here is my code so far:

class oupSeriesBlock extends Component {

    constructor(props) {
        super(props);
        this.state = {data: []};
    }

    getOupSeries() {
        console.log('mounted');
        apiFetch({
            path: 'wp/v2/oup_series'
        }).then((response) => {
            this.state = {data: response}
            console.log(this.state);
        })
    }

    componentDidMount() {
        this.getOupSeries();
    }

    render() {
        return(
            <div>
                Test
                {this.state.data.length > 0 ? this.state.data.map(data => <div>{data.id}</div>) : 'empty'}
            </div>
        )
    }
}

registerBlockType('cb/oup-series', {
    title: 'OUP Book Series',
    apiVersion: 2,
    category: 'cb-layout',
    icon: 'book-alt',
    description: 'Shows list of OUP book series',
    keywords: ['book', 'series', 'oup'],
    edit: () => {
        const blockProps = useBlockProps();
        return (
            <div {...blockProps}>
                <div>OUP Series List</div>
            </div>
        )
    },
    save: oupSeriesBlock
});

The block is available and rendering without error but is not displaying any data, only 'Test' is rendered.

The rest route is definitely functioning correctly as I've tested it, but componentDidMount() is never being called.

this.state.data.map also works correctly when constructed with dummy data.

I've read through multiple examples and tutorials, but I'm having trouble applying them to my problem, if anyone could help it would be greatly appreciated.

3
  • You can already implement this without any code by using a query block with a nested title/content/link blocks, choose the latest post block or post query block. in WP 6.0 it will even offer suggested patterns that can be modified using javascript. You can also implement it as a reusable block, a template part, or as a predefined pattern with locked blocks so that the user can't change them from your preferred design. A variant of the query block may also be a possibility.
    – Tom J Nowell
    Commented May 11, 2022 at 11:40
  • I would also strongly advise the user of function based components not class components. Lots of WordPress block APIs are only available as hooks, and your use of lifecycle methods is made redundant by the useEffect hook which can be used to fetch the posts more efficiently. Likewise it is an antipattern to talk directly to the REST API to fetch posts, there are more efficient cached APIs already built into the block editor for listing entities
    – Tom J Nowell
    Commented May 11, 2022 at 11:42
  • 1
    Also, your save component is meant to generate static HTML that gets saved in the database, you can't do API requests or make interactive changes in it. If you do then your block will always fail to validate. A save component can only use block attributes to generate its output. It may be necessary for you to render this block using PHP. Any javascript intended for the frontend has to run only on the frontend when the block is used, not in the editor, and not in the save component.
    – Tom J Nowell
    Commented May 11, 2022 at 11:43

1 Answer 1

1

This is because when your block was rendered for save the API call had not finished. Save components do not run on the frontend.

    render() {
        return(
            <div>
                Test
                {this.state.data.length > 0 ? this.state.data.map(data => <div>{data.id}</div>) : 'empty'}
            </div>
        )
    }

The purpose of a save component is to take your blocks attributes, and turn them into static HTML to be saved in the post content. None of the code in your save component executes on the frontend. Your save component cannot have side effects or lifecycle methods.

In order to implement fetching posts from the API endpoint when the block is viewed by frontend users, you need to enqueue a javascript file, identify all the places your block is used, and fetch the data. This is not different to when a shortcode has frontend javascript. The main difference here is that WordPress has hooks and filters to let you only enqueue the blocks frontend assets when it's actually used.

In summary:

  • Save components can't have state, side effects, or pull data from external sources such as editor state or remote APIs
  • Save components don't run on the frontend
  • Only the static serialised output of a save component is saved, not the react component itself
  • A save component must always return the same output given the same block attributes, or it is built incorrectly and will fail block validation
  • Use functional components so that you can use newer versions of the block API and hooks such as useBlockProps for better performance and features.
  • frontend javascript requires a separate frontend JS file to be enqueued
3
  • Thanks that's very helpful! I did misunderstand how everything works as I thought. I just assumed there was a single file solution, but I'm happy splitting the js up.
    – elliott_l
    Commented May 11, 2022 at 14:20
  • Also to reply to your other comments, it has to be a custom block as the client is not tech savvy so I'm making it as simple as possible for them, I'm quite experienced with wp + php in general, just starting to get my head round the more modern features. Could you maybe explain your useEffect comment in slightly more detail, I've never come across that :)
    – elliott_l
    Commented May 11, 2022 at 14:23
  • if you use a variation you can preconfigure and hide the options, and it will appear in the block inserter with your custom name and icon. As for useEffect, you pass it a function that gets executed outside of the render loop which can then do expensive tasks and update state to trigger a re-render, it also takes an array of dependencies so that when the dependencies change the effect re-runs, as well as options for cleaning up afterwards. It's documented on the react website with examples. Also look at useState
    – Tom J Nowell
    Commented May 11, 2022 at 14:43

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.