Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onAddBlock and onRemoveBlock event #41679

Open
foysalremon opened this issue Jun 12, 2022 · 8 comments
Open

onAddBlock and onRemoveBlock event #41679

foysalremon opened this issue Jun 12, 2022 · 8 comments
Labels
Needs Technical Feedback Needs testing from a developer perspective. [Type] Enhancement A suggestion for improvement.

Comments

@foysalremon
Copy link
Contributor

What problem does this address?

When we add a block it should hit a onAddBlock event by using which we can do something globally like save something unique for this block to database. We should also use onRemoveBlock to reverse that save when this block is deleted.

Example use case

Suppose, like creating a new email address, I want one of my inspector control field to hold a unique name for every instance of my block. It will be a auto generated field (ex. My Block 1) but user can also choose their own. And during savings it will get the previous instance values from database to check if this new one is unique. If not it will suggest user to change the name a bit to make it unique.

Also by deleting this instance of block, we should have a remove functionality to delete associated field value from the database

What is your proposed solution?

  • It could be a modified TextControl component to produce unique value by developer defined pattern. Ex. My Form 1, My Form 2
  • It could be 2 event onAddBlock and onRemoveBlock to enable developers to do some callback functionality. By this solution, they can do almost anything during those event, not only generate some unique field.
@akasunil
Copy link
Member

Agreed. Few months ago, i was working on one project where i had to detect the New Added/Removed blocks. Had used Block client id and subscribe method for continuous execution to find removed block. which was affected performance of editor. even though we couldn't find solution for new added block. This two methods will help a lot to handle Gutenberg actions for advance operations.

@stokesman
Copy link
Contributor

I'm not opposing the suggested feature though I'd like to mention it seems the editor.BlockEdit filter can be used as a way to roll your own version of this.

Example filter to fire callbacks for block addition and removal.
(( {
    compose: { createHigherOrderComponent },
    element: { createElement: el, Fragment, useEffect, useRef },
    hooks: { addFilter },
} ) => {
    addFilter(
        'editor.BlockEdit',
        'no41679/with-presence',
        createHigherOrderComponent( ( BlockEdit ) => ( props ) => {
            const ref = useRef();
            useEffect( () => {
                const previewContainer = ref.current.closest(
                    '.block-editor-block-preview__content-iframe'
                );
                // Bails when the block is inside a preview container.
                if ( previewContainer ) return;

                const { name, attributes, clientId } = props;
                console.log( 'block added' );
                // onBlockAdded( { name, attributes, clientId } );
                return () => {
                    console.log( 'block removed' );
                    // onBlockRemoved( { name, attributes, clientId } );
                }
            }, [] );
            return el(
                Fragment,
                null,
                el( BlockEdit, props ),
                el( 'meta', { itemProp: 'present', ref } ),
            );
        }, 'withPresence' )
    );
})( wp );

That example can be pasted into the console to test.

It also seems like the example use case might be quite related to #29693. There's no solution there yet but it could be worth subscribing to in case it leads to some features that might support your use case.

@foysalremon
Copy link
Contributor Author

@stokesman Thanks, your codes are helping me a lot for my current project. Yet, I believe it would be great to have some simple callback to use.

I'm not opposing the suggested feature though I'd like to mention it seems the editor.BlockEdit filter can be used as a way to roll your own version of this.

Example filter to fire callbacks for block addition and removal.
That example can be pasted into the console to test.

It also seems like the example use case might be quite related to #29693. There's no solution there yet but it could be worth subscribing to in case it leads to some features that might support your use case.

@kathrynwp kathrynwp added [Type] Enhancement A suggestion for improvement. Needs Technical Feedback Needs testing from a developer perspective. labels Jul 6, 2022
@MonteLogic
Copy link

MonteLogic commented Sep 13, 2022

Unless I am missing something it appears that the code you added is fired everytime the page is reloaded. Thus, it appears I am going to have to use the selectors and Redux functions to accomplish the task of an event that fires on block addition to Gutenberg editor.
fires-everytime-reload

When I reload the page the time from the Date method reloads and outputs to the console a new date.

@stokesman
Copy link
Contributor

Right. The blocks parsed from a saved post are added to the editor each page load. I'm not sure what would be the best approach to filter those out. wasBlockJustInserted might be a good selector to experiment with.

@CookiesForDevo
Copy link

wasBlockJustInserted does cover some use cases, particularly when duplicating a single block. But if you have a group that's being duplicated, it doesn't trigger for any of the InnerBlocks, despite also being inserted through duplication.

What about adding a wasBlockAncestorJustInserted?

@stokesman
Copy link
Contributor

stokesman commented Apr 16, 2024

…if you have a group that's being duplicated, it doesn't trigger for any of the InnerBlocks, despite also being inserted through duplication.

When wasBlockJustInserted returns true for a block then getBlock can be used to get its innerBlocks and treat them as having just been inserted. I tinkered with that and it works. Depending on if and how you'd need the onBlockRemoved callbacks to work then there’s a bit more to it.

Filter to fire added/removed callbacks only for blocks just inserted (and their innerBlocks)
(( {
    blockEditor: { store: blockEditorStore },
    compose: { createHigherOrderComponent },
    element: { createElement: el, useEffect },
    data: { useSelect },
    hooks: { addFilter },
} ) => {
    const onBlockAdded = ( block ) => {
        console.log( 'block added', block );
    }
    const onBlockRemoved = ( block ) => {
        console.log( 'block removed', block );
    }

    // Tracks added blocks. Really only needed if the `onBlockRemoved` callback
    // should only fire for blocks that were just inserted.
    const addedBlocks = new WeakSet;

    const addWithInnerBlocks = ( block ) => {
        onBlockAdded( block );
        addedBlocks.add( block );
        for ( const innerBlock of block.innerBlocks )
            addWithInnerBlocks( innerBlock );
    }

    addFilter(
        'editor.BlockEdit',
        'no41679/with-new-presence',
        createHigherOrderComponent( ( BlockEdit ) => ( props ) => {
            const { getBlock, wasBlockJustInserted } = useSelect( blockEditorStore );
            useEffect( () => {
                const { clientId } = props;
                const self = getBlock( clientId );
                if ( wasBlockJustInserted( clientId ) ) addWithInnerBlocks( self );
                return () => {
                    if ( addedBlocks.has( self ) ) onBlockRemoved( self );
                }
            }, [] );
            return el( BlockEdit, props );
        }, 'withNewPresence' )
    );
})( wp );

One neat thing about filtering to only blocks that have just been inserted is it also eliminates blocks inside preview containers which simplifies things in nice way (as compared to the original example I posted).

@jcaneteDI
Copy link

…if you have a group that's being duplicated, it doesn't trigger for any of the InnerBlocks, despite also being inserted through duplication.

When wasBlockJustInserted returns true for a block then getBlock can be used to get its innerBlocks and treat them as having just been inserted. I tinkered with that and it works. Depending on if and how you'd need the onBlockRemoved callbacks to work then there’s a bit more to it.

Filter to fire added/removed callbacks only for blocks just inserted (and their innerBlocks)
(( {
blockEditor: { store: blockEditorStore },
compose: { createHigherOrderComponent },
element: { createElement: el, useEffect },
data: { useSelect },
hooks: { addFilter },
} ) => {
const onBlockAdded = ( block ) => {
console.log( 'block added', block );
}
const onBlockRemoved = ( block ) => {
console.log( 'block removed', block );
}

// Tracks added blocks. Really only needed if the `onBlockRemoved` callback
// should only fire for blocks that were just inserted.
const addedBlocks = new WeakSet;

const addWithInnerBlocks = ( block ) => {
    onBlockAdded( block );
    addedBlocks.add( block );
    for ( const innerBlock of block.innerBlocks )
        addWithInnerBlocks( innerBlock );
}

addFilter(
    'editor.BlockEdit',
    'no41679/with-new-presence',
    createHigherOrderComponent( ( BlockEdit ) => ( props ) => {
        const { getBlock, wasBlockJustInserted } = useSelect( blockEditorStore );
        useEffect( () => {
            const { clientId } = props;
            const self = getBlock( clientId );
            if ( wasBlockJustInserted( clientId ) ) addWithInnerBlocks( self );
            return () => {
                if ( addedBlocks.has( self ) ) onBlockRemoved( self );
            }
        }, [] );
        return el( BlockEdit, props );
    }, 'withNewPresence' )
);

})( wp );
One neat thing about filtering to only blocks that have just been inserted is it also eliminates blocks inside preview containers which simplifies things in nice way (as compared to the original example I posted).

Amazing solution! Helped out alot.

Might be a good idea to add something like

const blocks = getBlocks();

// Makes sure blocks in page are already counted
blocks.forEach((block: any) => {
addWithInnerBlocks(block);
});

to make sure blocks already present in the page are included

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Technical Feedback Needs testing from a developer perspective. [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests

7 participants