DEV Community

Artur Piszek
Artur Piszek

Posted on • Originally published at piszek.com on

WordPress Data Views: Basic setup

WordPress has some sweet, sweet new APIs. One of them I am super excited about is Data Views. Data Views is the API to introduce modern table views into WordPress and I was excited to try it out for my Personal OS plugin.

Example: Custom taxonomy

In my example, I wanted to create a “Bucketlist” page in my WP-Admin to render a custom taxonomy that would hold my Bucketlist. We will need 3 elements:

  • We will need to hook into PHP to create a new WP-Admin page and render React there
  • React code to display Data Views
  • A build process to compile this React code.

React Gutenberg code

I will start with the actual Gutenberg React code. I put my code together using this fantastic tutorialand the code from WordPress Storybook

import { Icon, __experimentalHStack as HStack } from '@wordpress/components';
import domReady from '@wordpress/dom-ready';
import { useState, useMemo, createRoot } from '@wordpress/element';
// Per https://github.com/WordPress/gutenberg/tree/trunk/packages/dataviews :
// Important note If you're trying to use the DataViews component in a WordPress plugin or theme and you're building your scripts using the @wordpress/scripts package, you need to import the components from @wordpress/dataviews/wp instead of @wordpress/dataviews.
import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews/wp';
import { __ } from '@wordpress/i18n';
import { useEntityRecords } from '@wordpress/core-data';
import './style.scss';
import { trash, flag } from '@wordpress/icons';

function NotebookAdmin() {
    const [view, setView] = useState( {
        type: 'table',
        search: '',
        page: 1,
        perPage: 100,
        fields: ['name', 'description', 'flags', 'count'],
        layout: {},
        filters: [],
        sort: {
            order: 'asc',
            orderby: 'name',
        },
    } );

    // Our setup in this custom taxonomy.
    const fields = [
        {
            label: __( 'Name', 'your-textdomain' ),
            id: 'name',
            enableHiding: false,
            enableGlobalSearch: true,
            type: 'string',
        },
        {
            label: __( 'Description', 'your-textdomain' ),
            id: 'description',
            enableSorting: false,
            enableGlobalSearch: true,
            type: 'string',
        },
        {
            label: __( 'Flags', 'your-textdomain' ),
            id: 'flags',
            header: (
                <HStack spacing={ 1 } justify="start">
                    <Icon icon={ flag } />
                    <span>{ __( 'Flags', 'your-textdomain' ) }</span>
                </HStack>
            ),
            type: 'array',
            render: ( { item } ) => {
                return item.meta?.flag?.join( ', ' ) || '';
            },
            enableSorting: false,
        },
        {
            label: __( 'Count', 'your-textdomain' ),
            id: 'count',
            enableSorting: true,
            enableGlobalSearch: false,
            type: 'number',
        },
    ];

    // We will use the entity records hook to fetch all the items from the "notebook" custom taxonomy
    const { records } = useEntityRecords( 'taxonomy', 'notebook', {
        per_page: -1,
        page: 1,
        hide_empty: false,
    } );

    // filterSortAndPaginate works in memory. We theoretically could pass the parameters to backend to filter sort and paginate there.
    const { data: shownData, paginationInfo } = useMemo( () => {
        return filterSortAndPaginate( records, view, fields );
    }, [view, records] );

    return (
        <DataViews
            getItemId={ ( item ) => item.id.toString() }
            paginationInfo={ paginationInfo }
            data={ shownData }
            view={ view }
            fields={ fields }
            onChangeView={ setView }
            actions={ [
                {
                    id: 'delete',
                    label: __( 'Delete', 'your-textdomain' ),
                    icon: trash,
                    callback: async ( items ) => {
                        // Implement delete functionality
                        console.log( 'Delete items:', items );
                    },
                },
            ] }
            defaultLayouts={ {
                table: {
                    // Define default table layout settings
                    spacing: 'normal',
                    showHeader: true,
                },
            } }
        />
    );
}

domReady( () => {
    const root = createRoot( document.getElementById( 'bucketlist-root' ) );
    root.render( <NotebookAdmin /> );
} );

Enter fullscreen mode Exit fullscreen mode

In your plugin you have to import the components from @wordpress/dataviews/wp instead of @wordpress/dataviews. I expect this is a recent change and it will be further evolve. You also need to add

“@wordpress/dependency-extraction-webpack-plugin”: “^6.14.0”

To your devpependencies (check at the end of this post)

If you you import directly from @wordpress/dataviews you will get errors like these (click to expand)

column-header-menu.js:105 Uncaught TypeError: Cannot read properties of undefined (reading 'Group')
    at HeaderMenu (column-header-menu.js:105:44)
    at renderWithHooks (react-dom.js?ver=18.3.1:15496:20)
    at updateForwardRef (react-dom.js?ver=18.3.1:19255:22)
    at beginWork (react-dom.js?ver=18.3.1:21685:18)
    at HTMLUnknownElement.callCallback (react-dom.js?ver=18.3.1:4151:16)
    at Object.invokeGuardedCallbackDev (react-dom.js?ver=18.3.1:4200:18)
    at invokeGuardedCallback (react-dom.js?ver=18.3.1:4264:33)
    at beginWork$1 (react-dom.js?ver=18.3.1:27500:9)
    at performUnitOfWork (react-dom.js?ver=18.3.1:26606:14)
    at workLoopSync (react-dom.js?ver=18.3.1:26515:7)
react-dom.js?ver=18.3.1:18714 The above error occurred in the <AddFilterMenu> component:

    at AddFilterMenu (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:352:3)
    at div
    at FiltersToggle (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:742:3)
    at div
    at http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14024:47
    at UnforwardedView (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14903:3)
    at UnconnectedHStack (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:32221:23)
    at div
    at http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14024:47
    at UnforwardedView (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14903:3)
    at UnconnectedHStack (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:32221:23)
    at div
    at DataViews (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:2415:3)
    at BucketlistAdmin (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:14023:87)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
3
react-dom.js?ver=18.3.1:18714 The above error occurred in the <ForwardRef(HeaderMenu)> component:

    at HeaderMenu (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:3894:3)
    at th
    at tr
    at thead
    at table
    at ViewTable (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:4363:3)
    at DataViewsLayout (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:1526:69)
    at div
    at DataViews (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:2415:3)
    at BucketlistAdmin (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:14023:87)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
react-dom.js?ver=18.3.1:26972 Uncaught TypeError: Cannot read properties of undefined (reading 'Item')
    at add-filter.js:32:31
    at Array.map (<anonymous>)
    at AddFilterMenu (add-filter.js:31:1)
    at renderWithHooks (react-dom.js?ver=18.3.1:15496:20)
    at mountIndeterminateComponent (react-dom.js?ver=18.3.1:20113:15)
    at beginWork (react-dom.js?ver=18.3.1:21636:18)
    at beginWork$1 (react-dom.js?ver=18.3.1:27475:16)
    at performUnitOfWork (react-dom.js?ver=18.3.1:26606:14)
    at workLoopSync (react-dom.js?ver=18.3.1:26515:7)
    at renderRootSync (react-dom.js?ver=18.3.1:26483:9)
Enter fullscreen mode Exit fullscreen mode

PHP end

In PHP, you have to:

  • set up a WP-Admin page
  • enqueue your script
  • enqueue your style
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );

public function add_admin_menu(): void {
    add_menu_page(
        'Bucketlist',
        'Bucketlist',
        'read',
        'bucketlist',
        array( $this, 'render_admin_page' ),
        'dashicons-list-view'
    );
}

public function render_admin_page(): void {
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <div id="bucketlist-root"></div>
    </div>
    <?php
}

public function enqueue_admin_scripts( string $hook ): void {
    if ( 'toplevel_page_bucketlist' !== $hook ) {
        return;
    }

    $asset_file = plugin_dir_path( __FILE__ ) . 'js/build/admin.asset.php';
    $asset = require $asset_file;

    wp_enqueue_script(
        'bucketlist-admin',
        plugin_dir_url( __FILE__ ) . 'js/build/admin.js',
        $asset['dependencies'],
        $asset['version'],
        false
    );

    wp_enqueue_style(
        'bucketlist-admin',
        plugin_dir_url( __FILE__ ) . 'js/build/style-admin.css',
        array(),
        $asset['version']
    );
}
Enter fullscreen mode Exit fullscreen mode

Build code

This is my package.json. Remember to add ”@wordpress/dependency-extraction-webpack-plugin”: “^6.14.0″ in this specific version.

Otherwise, wp-scripts will treat dataviews as WordPress dependency and it won’t render in your app!

{
    "name": "bucketlist",
    "version": "1.0.0",
    "description": "Bucketlist module for PersonalOS",
    "scripts": {
        "build": "wp-scripts build js/src/admin.js --output-path=js/build",
        "start": "wp-scripts start js/src/admin.js --output-path=js/build"
    },
    "dependencies": {
        "@wordpress/components": "^25.0.0",
        "@wordpress/data": "^9.0.0",
        "@wordpress/dataviews": "^4.10.0",
        "@wordpress/element": "^5.0.0"
    },
    "devDependencies": {
        "@wordpress/scripts": "^30.7.0",
        "@wordpress/dependency-extraction-webpack-plugin": "^6.14.0"
    }
}
Enter fullscreen mode Exit fullscreen mode

For style tweaks and the rest you can refer to this fantastic walkthrough

The post WordPress Data Views: Basic setup appeared first on Artur Piszek.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay