0

I'm trying to diagnose a similar problem to this question, but in that case he wasn't depending on wp-api-fetch, and ... I'm pretty sure I am.

I'm getting the following error:

[Error] Unhandled Promise Rejection: TypeError: Object is not a function. (In 'Object(_wordpress_api_fetch__WEBPACK_IMPORTED_MODULE_5__["apiFetch"])', 'Object' is an instance of Object)

(full backtrace, below)

I should note that I'm new to both the REST API and ESNext/Gutenberg plugin development, so ... I may be missing something really obvious, like a comma :)

Here's the code:

import { __ } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import { TextControl } from '@wordpress/components';
import { apiFetch } from '@wordpress/api-fetch';

export default function Edit( props ) {
        const {
                attributes: { cwraggDataSourceType, cwraggDataSource,
                    cwraggLocalFile },
                setAttributes,
        } = props;

        const post_id = wp.data.select("core/editor").getCurrentPostId();

        const onChangeContent = async ( newUrl ) => {
                let localFileName = await apiFetch( {
                    path: '/cwraggb/v1/setremotedatasrc',
                    method: 'POST',
                    data: { 'url': newUrl,
                            'type': 'json',
                            'postId': post_id } } );
                ...
  
        };
        ...
}

I looked at the output of npm run start, and it seems to be including the dependencies in the build:

<?php return array('dependencies' => array('wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '566e4b7cb2f100542103b2b0e25aefae');

This is being built, and docker run, on MacOS 10.15.7.

~ % npm --version
6.14.8
~ % wp-env --version
2.1.0

Any ideas what's causing that error, and/or how I can further diagnose?

Full error message:

[Error] Unhandled Promise Rejection: TypeError: Object is not a function. (In 'Object(_wordpress_api_fetch__WEBPACK_IMPORTED_MODULE_5__["apiFetch"])', 'Object' is an instance of Object)
    dispatchException (wp-polyfill.js:7017)
    invoke (wp-polyfill.js:6738)
    asyncGeneratorStep (cwra-google-graph-block-admin.js:250)
    _next (cwra-google-graph-block-admin.js:272)
    (anonymous function) (cwra-google-graph-block-admin.js:279)
    Promise
    (anonymous function) (cwra-google-graph-block-admin.js:268)
    callCallback (react-dom.js:341)
    dispatchEvent
    invokeGuardedCallbackDev (react-dom.js:391)
    invokeGuardedCallback (react-dom.js:448)
    invokeGuardedCallbackAndCatchFirstError (react-dom.js:462)
    executeDispatch (react-dom.js:594)
    executeDispatchesInOrder (react-dom.js:616)
    executeDispatchesAndRelease (react-dom.js:719)
    forEach
    forEachAccumulated (react-dom.js:699)
    runEventsInBatch (react-dom.js:744)
    runExtractedPluginEventsInBatch (react-dom.js:875)
    handleTopLevel (react-dom.js:6026)
    dispatchEventForPluginEventSystem (react-dom.js:6121)
    dispatchEvent (react-dom.js:6150)
    dispatchEvent
    unstable_runWithPriority (react.js:2820)
    discreteUpdates$1 (react-dom.js:21810)
    discreteUpdates (react-dom.js:2357)
    dispatchDiscreteEvent (react-dom.js:6104)
    dispatchDiscreteEvent
5
  • Should those async functions doing calls not be inside a useEffect hook and then setting some local state? OOnce you fix your problem with unhandled promise rejections you'll run head first into that new problem. Eitherway, the problem is simple, apiFetch returns a promise, if something goes wrong, an error, a timeout, etc, it needs to be handled. Your code does not account for it failing and just assumes it will always work. The issue is unrelated to the build process or enqueing. I'd also advise switching to useSelect instead of using wp.data directly
    – Tom J Nowell
    Nov 3, 2020 at 11:32
  • I would also advise against directly making changes to a site in blocks. Changes shouldn't be saved until you save, though retrieving information is fine if done correctly
    – Tom J Nowell
    Nov 3, 2020 at 11:35
  • Still working on your first comment @Tom and your answer below (which I suspect will solve my problem). This second comment makes a good point, so I've opened it as another question Nov 3, 2020 at 16:17
  • I'm not sure what the proper etiquette/action here is, @TomJNowell. Your comment above and answer below were very useful, caused me to do a lot of reading, and I learned a lot. But the "answer" turned out to be much simpler — import { apiFetch } from '@wordpress/api-fetch'; doesn't work; you can't have the brackets — import apiFetch from '@wordpress/api-fetch'; fixed it ("why" is a whole other question). Do I upvote your answer, because it's great, even though not "the" answer? Do I post this as "answer myself"? Like I said, not sure how to handle this. Nov 7, 2020 at 8:56
  • Post it as an answer! You can always upvote mine, though you'll notice my code snippet doesn't put brackets around apiFetch either
    – Tom J Nowell
    Nov 7, 2020 at 11:14

2 Answers 2

0

The root problem is a misunderstanding of how async functions work.

const onChangeContent = async ( newUrl ) => {

onChangeContent is not a function/async function. It is an object, specifically a Promise.

The async and await are just shorthand. These 2 functions are identical:

async function foo() {
   return 1
}

function foo() {
   return Promise.resolve(1)
}

Since React expects a function, it instead gets a Promise and fails.

Additionally, because if your apiFetch call failed for any reason, there is no error handler on the promise returned.

Not only that, but the code has been placed raw in a React component. I strongly advise against this. Use useEffect instead, and combine it with local state if you need to pass information back to the component. useEffect calls the function after the React component has been rendered, so it does not block execution. It also prevents repeated unnecessary calls.

For example, here we fetch posts, display loading status, etc:

import React from 'react';
import apiFetch from '@wordpress/api-fetch';
import { useSelect } from '@wordpress/data';
import { useState, useEffect, useRef } from '@wordpress/element';
import { addQueryArgs } from '@wordpress/url';

function PostList() {
    // are we loading the posts?
    const [ isLoading, setLoading ] = useState( true );
    // the posts we're displaying
    const [ posts, setPosts ] = useState([]);
    // we don't want to update state on an unmounted component
    const isStillMounted = useRef();

    // fetch the posts
    useEffect(() => {
        isStillMounted.current = true;
        apiFetch( {
            path: addQueryArgs( `/wp/v2/posts`, { } ),
        } )
            .then( ( data ) => {
                if ( isStillMounted.current ) {
                    setPosts( data );
                    setLoading( false );
                }
            } )
            .catch( () => {
                if ( isStillMounted.current ) {
                    setPosts( [] );
                    setLoading( false );
                }
            } );
    }, [setPosts, setLoading, isStillMounted]);

    if ( isLoading ) {
        return <div>Loading...</div>;
    }

    return <div>{ .. use posts variable here ... }</div>

}

Keep in mind these are not block editor specific things, they're general JS React things.

Notice:

  • That we have a catch clause on the apiFetch call
  • That the fetch call is a side effect, so it was moved to a useEffect hook
  • The only things the effect touches outside the function are to update state
  • That effect will not be called again unless the 3 dependencies change, or a new instance of that component is mounted
  • A loading state has been added
  • We used useRef to track if the component is mounted. If your block was removed before the API call finished, this prevents an error
0

Although I still don't understand ESNext/JavaScript well enough to know why this is the answer, it did turn out to be of the "missing a comma" category — specifically, I couldn't have brackets in the import. So

import { __ } from '@wordpress/i18n';
import { Fragment } from '@wordpress/element';
import { TextControl } from '@wordpress/components';
import apiFetch from '@wordpress/api-fetch';

solved the problem. That said, Tom's answer is worth reading as well (and includes this detail).

Your Answer

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

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