5

How can I display an admin notification?

I want to do some actions on the save_post_post action and want to inform the admin about the success and details about the action after that.

I tried so far:

add_action('save_post_post', "do_Stuff");

function do_stuff( $postId ){

     session_start();
    //do stuff and create some $msg;

     set_transient( 'temporary_message', $msg, 60*60*12 );
     add_action( 'admin_notices', 'show_notice');

}

function show_notice(){
    ?>
    <div class="notice notice-error">
        <p>Fehler: <?=get_transient( 'temporary_message' )?></p>
    </div>
    <?php
}

The Thing is, this notification is not shown, because the browser does not reload after saving, because the saving happens via ajax ... my save_post_post hook is called.

I already tried to create and output some JSON, as I get an error-admin-notification if I output any text, that my JSON is invalid. But When I send JSON and inspect the network traffic in Chrome, I don't see any response and .. the earlier error is still there.

How can I show an Admin Notification anyways?

5
  • and it seems, that my hooked function is called twice...
    – helle
    Apr 1, 2020 at 15:15
  • Yes, save_post and almost every other hook associated with publishing or updating run twice. So it might be wiser to try to change some of the built-in Editor text than try to show an admin notice on any hook.
    – WebElaine
    Apr 1, 2020 at 21:02
  • Thanks for the comment. What is the builit-in Editor and how would that work? How can I do only one action, when the hook is called twice? i.e. I don't want to send two emails, if the action triggers emails...
    – helle
    Apr 2, 2020 at 8:48
  • Not built-in Editor (that just means the Block Editor). Built-in Editor text. See this q&a for how to change some of the text - you may be able to change text in other places as well. wordpress.stackexchange.com/questions/328121/…
    – WebElaine
    Apr 2, 2020 at 13:12
  • @helle if you found solution, please share it
    – ViliusL
    Mar 18, 2021 at 11:43

1 Answer 1

3

Presenting Notices

Gutenberg provides a mechanism for displaying notices in the form of the Notices Data package. In Gutenberg's editors, the package's selectors and action creators are exposed on the core/notices store, and can be accessed by any standard means therein, e.g.:

  • useDispatch( 'core/notices' ) hook in functional components (including the edit() function of a block type, and the render() function of a block editor plugin registered using @wordpress/plugins' registerPlugin() function).
  • withDispatch() HOC factory for class-based components.
  • dispatch( 'core/notices' ) for any other code.

Functional Component Example

import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';

const MyComponent = props => {
  const [ error, setError ] = useState( null );

  if( error ) {
    useDispatch( 'core/notices' ).createNotice( {
      status: 'error',
      content: error,
    } );

    setError( null );
  }

  // ...
}


Subscribing to a Save Operation

I am not certain that this is the best mechanism for accomplishing this - and whether or not it is appropriate for your code really depends on your specific use-case - but in general we often implement the concept of "listening for events" in Gutenberg and Redux by instead subscribing to application state updates and testing for specific changes.

By that logic, one way to execute code when a save operation completes is to observe the change in return value from the isPostSaving() selector on the core/editor store:

import { useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

const MyComponent = props => {
  const is_saving = useSelect( select => select( 'core/editor' ).isSavingPost() );
  const [ was_saving, setWasSaving ] = useState( is_saving );

  if( was_saving ) {
    // If state transitioned from saving -> not saving, execute post-save logic.
    if( ! is_saving ) {
      setWasSaving( false );

      console.log( 'Post Save Finished' );
    }
  }
  else if( is_saving ) {
    // If state transitioned from not saving -> saving, set was_saving.
    setWasSaving( true );
  }

  // ...
}

We could also extract that logic to a custom hook in order to make it more portable:

const usePostSaved = () => {
  const is_saving = useSelect( select => select( 'core/editor' ).isSavingPost() );
  const [ was_saving, setWasSaving ] = useState( is_saving );

  if( was_saving ) {
    if( ! is_saving ) {
      setWasSaving( false );

      return true;
    }
  }
  else if( is_saving ) {
    setWasSaving( true );
  }

  return false;
}

const MyComponent = props => {
  const saved = usePostSaved();

  if( saved )
    console.log( 'Post Save Finished' );

  // ...
}

Acquiring Notices From the Back-End

If you have back-end code generating notices, one solution for displaying them in the editor would be to queue them in transients, then expose those transients for an asynchronous request from your JavaScript by implementing a custom REST API endpoint or a wp_ajax_ handler. In either scenario, the @wordpress/api-fetch package can help you succinctly perform the network request.

It might also be possible to leverage the Heartbeat API to this end, or otherwise expose the notices in a manner which could be accessed via Gutenberg's built-in selectors (like adding them to post meta, for example).

Example: AJAX Handler/Block Editor Plugin

A save action hook and AJAX handler to provide the notice might look like this:

function wpse36297_handle_post_save( $post_id, $post, $update ) {
  // ...
  if( $a_critical_error ) {
    $notice = [
      'status'        => 'error',
      'message'       => __( 'Things done blew up :/', 'wpse36297' ),
      'isDismissable' => true,
    ];
  }
  else {
    $notice = [
      'status'        => 'success',
      'message'       => __( 'This was a triumph! \\o/', 'wpse36297' ),
      'isDismissable' => true,
    ];
  }

  set_transient( 'wpse36297-notice-post_' . $post_id, $notice, 60*60 );
}
add_action( 'save_post_post', 'wpse36297_handle_post_save', 10, 3 );

function wpse36297_ajax_get_post_notice() {
  $post_id = (int) $_GET['post_id'];

  if( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_send_json_error(
      [
        'msg' => __( 'User is not permitted to edit this post.', 'wpse36297' ),
      ],
      403
    );
  }

  $notice = get_transient( 'wpse36297-notice-post_' . $post_id );

  if( $notice )
    delete_transient( 'wpse36297-notice-post_' . $post_id );

  wp_send_json_success( $notice );
}
add_action( 'wp_ajax_wpse36297-get-post-notice', 'wpse36297_ajax_get_post_notice' );

We can then make a request to that handler once state updates indicate that a save has just completed.

Aside: the code below uses wp.ajax.settings.url which is provided by the wp-util script. If you are using @wordpress/scripts/@wordpress/create-block, this script will not be automatically added to your script dependencies in the .asset.php file as it is not a Gutenberg package.

import apiFetch from '@wordpress/api-fetch';
import { registerPlugin } from '@wordpress/plugins';
import { useState, useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';

const fetchPostNotice = async ( post_id ) => {
  const url = new URL( window.location.origin + wp.ajax.settings.url );

  url.searchParams.append( 'action', 'wpse36297-get-post-notice' );
  url.searchParams.append( 'post_id', post_id );

  const res = await apiFetch( { url: url.toString() } );

  if( ! res.success )
    return { status: 'error', message: res.data.msg };

  return res.data;
};

registerPlugin(
  'wpse362975',
  {
    render: () => {
      const { createNotice } = useDispatch( 'core/notices' );
      const { getCurrentPostId } = useSelect( 'core/editor' );
      const is_saving = useSelect( select => select( 'core/editor' ).isSavingPost() );
      const [ was_saving, setWasSaving ] = useState( is_saving );

      useEffect(
        async () => {
          if( was_saving && ! is_saving ) {
            setWasSaving( false );

            const notice = await fetchPostNotice( getCurrentPostId() );

            if( ! notice )
              return;

            const { status, message, ...options } = notice;
            createNotice( status, message, options );
          }
          else if( is_saving && ! was_saving ) {
            setWasSaving( true );
          }
        },
        [ is_saving, was_saving ]
      );

      return null;
    }
  }
);
3
  • Not working, it gives such error: Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
    – partyzant
    Mar 9, 2022 at 20:57
  • @partyzant That error is thrown when you attempt to use any React hook within a class-based component/entirely outside of a component. The code above explicitly uses functional components and functions fine for me. The issue likely lies in your specific implementation - if you'd like to open a new question containing your implementation I'll see if I can spot the problem!
    – bosco
    Mar 24, 2022 at 0:42
  • @bosco - I get the error message Uncaught SyntaxError: Cannot use import statement outside a module. How other ways can I reference @wordpress/data
    – Motivated
    May 14 at 5:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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