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.

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay