admin管理员组

文章数量:1291224

In a plugin I'm developing, I registered a custom REST API route to get some post data passing a post id:

function rest_api_init() {
    register_rest_route( 'custombase/v1', '/post/(?P<id>[\d]+)', array(
        'methods'             => 'GET',
        'callback'            => 'get_post_rest',
        'permission_callback' => '__return_true',
        'args'                => array(
            'id' => array(
                'validate_callback' => function( $param, $request, $key ) {
                    return is_numeric( $param );
                }
            ),
        ),
    ) );
}

function get_post_rest( WP_REST_Request $request ) {
    $post = get_post( $request['id'] );
    if ( is_wp_error( $post ) ) {
        return $post;
    }

    $data = array(
        'ID'    => $post->ID,
        'title' => $post->post_title,
    );

    $response = new WP_REST_Response( $data, 200 );

    return $response;
}

I checked and the route is registered correctly as I can get info visiting, for example, the following URL:


This is the result:

{"ID":1239,"title":"My example post title"}

As I understand, I need to use the apiFetch utility to make REST API requests to custom routes in Gutenberg. This is what I'm doing:

const mypost = apiFetch( { path: 'custombase/v1/post/1239' } ).then( ( post ) => {
    return post;
} );

But, instead of post object, I'm getting a Promise with the post object inside when I do a console.log( mypost ):

Promise { <state>: "pending" }
​<state>: "fulfilled"
​<value>: Array [ {…} ]
​​0: Object { ID: 1239, title: "My example post title" }
​​length: 1
​​<prototype>: Array []

How can I get the post object instead?

In a plugin I'm developing, I registered a custom REST API route to get some post data passing a post id:

function rest_api_init() {
    register_rest_route( 'custombase/v1', '/post/(?P<id>[\d]+)', array(
        'methods'             => 'GET',
        'callback'            => 'get_post_rest',
        'permission_callback' => '__return_true',
        'args'                => array(
            'id' => array(
                'validate_callback' => function( $param, $request, $key ) {
                    return is_numeric( $param );
                }
            ),
        ),
    ) );
}

function get_post_rest( WP_REST_Request $request ) {
    $post = get_post( $request['id'] );
    if ( is_wp_error( $post ) ) {
        return $post;
    }

    $data = array(
        'ID'    => $post->ID,
        'title' => $post->post_title,
    );

    $response = new WP_REST_Response( $data, 200 );

    return $response;
}

I checked and the route is registered correctly as I can get info visiting, for example, the following URL:

https://example/wp-json/custombase/v1/post/1239

This is the result:

{"ID":1239,"title":"My example post title"}

As I understand, I need to use the apiFetch utility to make REST API requests to custom routes in Gutenberg. This is what I'm doing:

const mypost = apiFetch( { path: 'custombase/v1/post/1239' } ).then( ( post ) => {
    return post;
} );

But, instead of post object, I'm getting a Promise with the post object inside when I do a console.log( mypost ):

Promise { <state>: "pending" }
​<state>: "fulfilled"
​<value>: Array [ {…} ]
​​0: Object { ID: 1239, title: "My example post title" }
​​length: 1
​​<prototype>: Array []

How can I get the post object instead?

Share Improve this question edited May 28, 2021 at 21:10 leemon asked May 28, 2021 at 11:53 leemonleemon 2,0324 gold badges23 silver badges51 bronze badges 6
  • developer.mozilla/en-US/docs/Web/JavaScript/Reference/… – Jacob Peattie Commented May 28, 2021 at 12:49
  • I'm not sure why you're using a custom endpoint to return post data, why not register the extra fields you wanted and use the get entity API? Then your posts would automatically be retrieved and would be incorporated in the standard post objects, and you could even use standard APIs to save the data. Doing it this way duplicates a lot of internal functionality, and introduces new problems, e.g. how do you tell the different between the current data vs the unsaved edited data? – Tom J Nowell Commented May 28, 2021 at 13:09
  • as for the promise, this is an asynchronous mechanism, you need to wait for the promise to complete, but that's not a WordPress problem, that's a general javascript problem (and one you shouldn't be trying to solve as this approach is wrong, see my previous comment) – Tom J Nowell Commented May 28, 2021 at 13:11
  • see developer.wordpress/reference/functions/register_rest_field/… for an example. If you're doing this because you believe you're improving performance by only retrieving the ID and title, then you are mistaken, the entire post will be fetched including its post meta and terms when get_post is called, and even if it was faster, it would still be slower than just bundling the data via the post endpoint and having it pre-added by the block editor so no additional requests are needed – Tom J Nowell Commented May 28, 2021 at 13:13
  • Yes, I know how to use getEntityRecord to get post data and I'm sure it's way more optimal than using a custom endpoint, but it requires the post type to be specified. I just wanted to try using an agnostic endpoint that doesn't need the post type to be provided. – leemon Commented May 28, 2021 at 13:29
 |  Show 1 more comment

1 Answer 1

Reset to default 6

I'm getting a Promise with the post object inside when I do a console.log( mypost )

Yes, because apiFetch() indeed returns a Promise object, and apiFetch() doesn't assign the response received from the server to the mypost constant, and the return value of your then() callback is also not going to be assigned to the mypost variable.

How can I get the post object instead?

As suggested by React, you would want to add mypost as a local state in your component, then make your XHR/AJAX request (using apiFetch(), the native window.fetch(), Axios or whatever) in the componentDidMount() method of a class component, or use the useEffect hook in a function component.

Example using apiFetch() with useEffect:

import { useState, useEffect } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';

function MyComponent( { post_id } ) {
    const [ error, setError ]       = useState( null );
    const [ mypost, setPost ]       = useState( null );
    const [ isLoaded, setIsLoaded ] = useState( false );

    useEffect( () => {
        apiFetch( { path: `custombase/v1/post/${ post_id }` } ).then(
            ( result ) => {
                setIsLoaded( true );
                setPost( result );
            },
            ( error ) => {
                setIsLoaded( true );
                setError( error );
            }
        );
    }, [ post_id ] );

    if ( error ) {
        return <p>ERROR: { error.message }</p>;
    } else if ( ! isLoaded ) {
        return <p>Loading post { post_id }..</p>;
    } else if ( mypost && mypost.id ) {
        return <h3>Post <i>{ mypost.title || '#' + mypost.id }</i> loaded!</h3>;
    }
    return <p>No such post</p>;
}
// Sample usage: <MyComponent post_id="1239" />

Alternate Solution: Add an entity for your custom endpoint into the list of entities in the block editor.

  1. Add the entity using addEntities():

    import { dispatch } from '@wordpress/data';
    
    dispatch( 'core' ).addEntities( [{
        baseURL: '/custombase/v1/post',
        // The 'post' is not a post type - it's the "post" as in /post above. Also, "kind"
        // and "name" are not documented, so let's assume they form the above baseURL..
        kind: 'custombase/v1',
        name: 'post',
        label: 'Post or whatever',
    }] );
    
    // You can, for example, call the above (i.e. add the entity) before you register your
    // block type.
    
  2. Then use getEntityRecord() to fetch post data from the endpoint:

    const mypost = select( 'core' ).getEntityRecord( 'custombase/v1', 'post', 1239 );
    // Note that getEntityRecord() caches the results.
    
  3. And note that for getEntityRecord() to work correctly, your endpoint callback must use the lowercase id and not ID:

    // In the get_post_rest() function:
    
    $data = array(
        'id'    => $post->ID, // use id and *not* ID
        'title' => $post->post_title,
    );
    

So for example using useSelect, the above component (MyComponent) could now look like so:

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

function MyComponent( { post_id } ) {
    const { mypost, isLoading } = useSelect( ( select ) => {
        const args = [ 'custombase/v1', 'post', post_id ];

        return {
            mypost: select( 'core' ).getEntityRecord( ...args ),
            isLoading: select( 'core/data' ).isResolving( 'core', 'getEntityRecord', args )
        };
    }, [ post_id ] );

    if ( isLoading ) {
        return <p>Loading post { post_id }..</p>;
    } else if ( mypost && mypost.id ) {
        return <h3>Post <i>{ mypost.title || '#' + mypost.id }</i> loaded!</h3>;
    }
    return <p>No such post</p>;
}

本文标签: javascriptUsing apiFetch for retrieving post data in Gutenberg