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.

Top comments (0)