1

Rephrased the question on August 9th.

This is my first attempt to work with Wordpress blocks and also my first time working with React.

The code below adds the possibility to conditionally show blocks.

My question is related to the "select pages" section, or the "useSelect".

I show a list of pages which have been selected previously. I also show a list of pages based on a search. Whenever someone selects a page I move the checkbox from the search list to the selected list.

This works great except when I save the page/post whatever. Somehow when the "initialSelectedPages" gets rendered it uses the old attributes... I have to refresh the page, or re-select the block in order to show the the correct updated list.

But I have no idea why. Any ideas?

Also, as this is my first React code, any feedback is welcome!

This is my code how I load the js:

add_action( 'enqueue_block_editor_assets', function() {
    wp_register_script(
        'sim-block-filter',
        plugins_url('blocks/build/index.js', __DIR__),
        [ 'wp-blocks', 'wp-dom', 'wp-dom-ready', 'wp-edit-post' ],
        STYLE_VERSION
    );
    wp_enqueue_script( 'sim-block-filter' );
});

This is the js

const { __ }                            = wp.i18n;
const { createHigherOrderComponent }    = wp.compose;
const { Fragment }                      = wp.element;
const { InspectorControls }             = wp.blockEditor;
const { PanelBody, ToggleControl, CheckboxControl }      = wp.components;
import { SearchControl, Spinner, __experimentalInputControl as InputControl } from '@wordpress/components';
import {useState, useEffect } from "@wordpress/element";
import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';  

// Add attributes
function addFilterAttribute(settings) {
    if (typeof settings.attributes !== 'undefined') {
        settings.attributes = Object.assign(settings.attributes, {
            hideOnMobile: {
                type: 'boolean',
            },
            onlyOnHomePage: {
                type: 'boolean',
            },
            onlyLoggedIn: {
                type: 'boolean',
            },
            onlyOn: {
                type: 'array'
            },
            phpFilters: {
                type: 'array'
            }
        });
    }
    return settings;
}
 
wp.hooks.addFilter(
    'blocks.registerBlockType',
    'sim/block-filter-attribute',
    addFilterAttribute
);

// Add controls to panel
const blockFilterControls = createHigherOrderComponent((BlockEdit) => {
    return ( props ) => {
        const { attributes, setAttributes, isSelected } = props;

        // Only work on selected blocks
        if(!isSelected){
            return (
                <Fragment>
                    <BlockEdit { ...props } />
                </Fragment>
            );
        }

        //console.log(attributes.onlyOn)
        if(attributes.onlyOn == undefined){
            attributes.onlyOn = [];
        }

        /** FUNCTIONS */
        const [ searchTerm, setSearchTerm ]     = useState( '' );

        // Selected page list
        const { initialSelectedPages, selectedPagesResolved} = useSelect(
            ( select) => {
                // Find all selected pages
                const selectedPagesArgs = [ 'postType', 'page', {include : attributes.onlyOn} ];

                //console.log(attributes.onlyOn);

                return {
                    initialSelectedPages: select( coreDataStore ).getEntityRecords(
                        ...selectedPagesArgs
                    ),
                    selectedPagesResolved: select( coreDataStore ).hasFinishedResolution(
                        'getEntityRecords',
                        selectedPagesArgs
                    )
                };
            },
            []
        );

        // Search page list
        const { pages, pagesResolved } = useSelect(
            ( select) => {
                // do not show results if not searching
                if ( !searchTerm ) {
                    return{
                        pages: true,
                        pagesResolved: []
                    }
                }

                // find all pages excluding the already selected pages
                const query = {
                    exclude : attributes.onlyOn,
                    search  : searchTerm
                };

                const pagesArgs         = [ 'postType', 'page', query ];

                return {
                    pages: select( coreDataStore ).getEntityRecords(
                        ...pagesArgs
                    ),
                    pagesResolved: select( coreDataStore ).hasFinishedResolution(
                        'getEntityRecords',
                        pagesArgs
                    )
                };
            },
            [ searchTerm ]
        );

        const PageSelected = function(checked){
            let newPages    = [...attributes.onlyOn];

            if(checked){
                // Add to stored page ids
                newPages.push(this);

                // Add to selected pages list
                let newSelectedPages    = [...selectedPages];
                newSelectedPages.push(pages.find( p => p.id == this));
                setSelectedPages(newSelectedPages);
            }else{
                newPages    = newPages.filter( p => {return p != this} );
            }

            setAttributes({onlyOn: newPages});
        }

        const GetSelectedPagesControls = function(){
            if(attributes.onlyOn.length > 0){
                return (
                    <>
                        <i> {__('Currently selected pages', 'sim')}:</i>
                        <br></br>
                        
                        <BuildCheckboxControls hasResolved={ selectedPagesResolved } items={initialSelectedPages} showNoResults={false}/>
                    </>
                );
            }else{
                return '';
            }
        }

        const BuildCheckboxControls = function({ hasResolved, items, showNoResults= true }){
            if ( ! hasResolved ) {
                return(
                    <>
                    <Spinner />
                    <br></br>
                    </>
                );
            }
        
            if ( ! items?.length ) {
                if(showNoResults){
                    if ( !searchTerm ) {
                        return '';
                    }
                    return <div> {__('No search results', 'sim')}</div>;
                }

                return '';
            }
            
            return items?.map( ( page ) => {
            
                return (<CheckboxControl
                    label       = {decodeEntities( page.title.rendered )}
                    onChange    = {PageSelected.bind(page.id)}
                    checked     = {attributes.onlyOn.includes(page.id)}
                />)
            } )
        }

        /** HOOKS */
        const [ selectedPages, setSelectedPages ]                   = useState( [] );
        const [ selectedPagesControls, setSelectedPagesControls ]   = useState( GetSelectedPagesControls() );

        // Update selectedPagesControls on page resolve
        useEffect(() => {
            setSelectedPages(initialSelectedPages);
        }, [ selectedPagesResolved ]);

        // Update selectedPagesControls on check/uncheck
        useEffect(() => {            
            setSelectedPages( selectedPages.filter( p => {return attributes.onlyOn.includes(p.id)} ));
        }, [ attributes.onlyOn ]);

        useEffect( 
            () => {
                setSelectedPagesControls(BuildCheckboxControls({hasResolved: selectedPagesResolved, items: selectedPages, showNoResults: false}));
            }, 
            [selectedPages]
        );

        return (
            <Fragment>
                <BlockEdit { ...props } />
                <InspectorControls>
                    <PanelBody title={ __( 'Block Visibility' ) }>

                        <strong>{__('Select pages', 'sim')}</strong><br></br>
                        {__('Select pages you want this widget to show on', 'sim')}.<br></br>
                        {__('Leave empty for all pages', 'sim')}<br></br>
                        <br></br>
                        {selectedPagesControls}
                        <i>{__('Use searchbox below to search for more pages to include', 'sim')}</i>
                        < SearchControl onChange={ setSearchTerm } value={ searchTerm } />
                        < BuildCheckboxControls hasResolved= {pagesResolved} items= {pages} />
                    </PanelBody>
                </InspectorControls>
            </Fragment>
        );
    };
}, 'blockFilterControls');
 
wp.hooks.addFilter(
    'editor.BlockEdit',
    'sim/block-filter-controls',
    blockFilterControls
);
New contributor
Tsjippy is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
4
  • its start is promising. Good question and well formatted. But don't be seen as a BOT... Enter a username and update your profile... Soon someone will comment on your question. I don't talk about it because I don't have the domain for it. Hug Aug 6 at 11:42
  • He is seen by the system how? CSS, PHP or Javascript... If it's JavaScript you can just quote it and put it to load below the page. But I've never heard of optimizing React... You should just put it to load at the correct time on the page. So that it doesn't interrupt the loading of it... As I said... Handle it as if it were CSS or Javascript... Look for more about it. Aug 6 at 11:47
  • thank you, I have updated the question
    – Tsjippy
    2 days ago
  • I'm a little confused, your question title talks about making your react code faster and asking for optimisations, but then I read your questions text it's instead asking about stale/old attributes and having to refresh the page. Can you make sure your question is consistent? Questions here should be clear and specific
    – Tom J Nowell
    19 hours ago

1 Answer 1

0

Of course it never changes, because you told it that it never changes:

        // Selected page list
        const { initialSelectedPages, selectedPagesResolved} = useSelect(
            ( select) => {
                ....
            },
      ----->[] <-----
        );

You declared the dependencies as an empty array, and empty arrays won't change, so this hook won't update when your attributes update because you didn't specify the attributes that were going to update.

E.g.

const { foo } = useSelect(
    ( select ) => {
        return { foo: bar+1 };
    },
    [ bar ]
);

Here, foo will have the value of bar + 1. That function will not run until the contents of the dependency array changed. This avoids it constantly running. The only reason foo changes when bar changes is because I declared bar as a dependency.

So when you do this:

                const selectedPagesArgs = [ 'postType', 'page', {include : attributes.onlyOn} ];

It has no idea that when attributes.onlyOn changes it has to redo this, because you did not declare it in the list of dependencies.

Note that this is similar if not the same semantics as useEffect.

Here is the official documentation with examples and explanations:

https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#useselect

Your Answer

Tsjippy is a new contributor. Be nice, and check out our Code of Conduct.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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