why is apiFetch throwing Unhandled Promise Rejection: TypeError: Object is not a function

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