0

I asked a previous question, and got a helpful answer that got me going quite a ways. The present problem is unrelated, but IMO reflects the dearth of documentation suitable for those of us who aren't familiar with some of the nuances of javascript, are completely new to Gutenberg development, and yet need to tweak the new Wordpress here and there. I'll state right up front that I'm new to javascript, and suspect that my issue is related to that fact.

As stated in my previous question, I'm working on modifying the featured image control in the admin edit screen of a custom post type. I want to add a checkbox, and a text input field to capture a user-supplied resource URL. I have a javascript file that loads on the page in question, along with all the requisite dependencies to work with Gutenberg, JSX, and all of the react-based components (react-devel, react-dom, babel, wp-blocks, wp-i18n, wp-element, wp-editor, wp-hooks). I've also got a filter in place that sets the script type of my .js file to 'text/babel' so JSX interprets correctly. I don't think I've missed anything.

Here's a version of my .js file that displays a heading and rich text field. It's not what I'm after but it does work and there aren't any complaints in the javascript console. At this point I'm still trying to figure out the building blocks I need, and how to use them.

window.addEventListener("load", function(event){
  console.log("featured_image.js loaded and functional...");
});

const el = wp.element.createElement;
// const withState = wp.compose.withState;
const setState = wp.data.setState;
const withSelect = wp.data.withSelect;
const withDispatch = wp.data.withDispatch;
const { CheckboxControl } = wp.editor;
const { RichText } = wp.editor;
const { useState } = wp.element;
const { TextControl } = wp.components
const { withState } = wp.compose;


wp.hooks.addFilter(
    'editor.PostFeaturedImage',
    'dsplugin/featured-image-as-video',
    wrapPostFeaturedImage
);


//this works
const MyRichTextField =
    <RichText
      placeholder="placeholder text"
    />
;

function wrapPostFeaturedImage( OriginalComponent ) {
    return function( props ) {
        return (
            el(
                wp.element.Fragment,
                {},
                // 'Prepend above',
                el(
                    OriginalComponent,
                    props
                ),
                <strong>this is a test</strong>,
                // MyCheckboxControl
                MyRichTextField
            )
        );
    }
}

Here's what the output looks like...

featured image sidebar area

When I try to add a checkboxControl though, using the same methodology I used for the RichText field, I run into problems. This code...

const MyCheckboxControl =
    const [ isChecked, setChecked ] = useState( true );
    <CheckboxControl
        heading="User"
        label="Is author"
        help="Is the user a author or not?"
        checked={ isChecked }
        onChange={ setChecked }
    />
;

Doesn't work. And the javascript console says...

SyntaxError: ... Unexpected token (28:4)
  27 | const MyCheckboxControl = () => (
> 28 |     const [ isChecked, setChecked ] = useState( true );
     |     ^
  29 |     <CheckboxControl
  30 |         heading="User"
  31 |         label="Is author"

However, this is exactly how the Gutenberg documentation says we should implement a CheckboxControl. At least when creating blocks. Are things different in the sidebar? I tried a TextControl, as follows...

const MyTextControl = withState( {
    className: '',
} )( ( { className, setState } ) => (
    <TextControl
        label="Additional CSS Class"
        value={ className }
        onChange={ ( className ) => setState( { className } ) }
    />
) );

No syntax error for the above definition, but when I try to use it down in the wrapPostFeaturedImage() function as follows:

function wrapPostFeaturedImage( OriginalComponent ) {
    return function( props ) {
        return (
            el(
                wp.element.Fragment,
                {},
                // 'Prepend above',
                el(
                    OriginalComponent,
                    props
                ),
                <strong>this is a test</strong>,
                // MyCheckboxControl
                MyTextControl
            )
        );
    }
}

I get the following warning below in the console, and nothing shows up after the "this is a test" heading.

Warning: Functions are not valid as a React child. 
This may happen if you return a Component instead of <Component /> from render. 
Or maybe you meant to call this function rather than return it.

I feel like I have a fundamental disconnect with what's going on here. What am I missing?

  • Well I just figured one thing out. "MyTextControl" should have been "<MyTextControl />". Now it renders. I guess one calls the constant as if it were a tag... – Andrew Mar 15 at 4:08
2

Warning (or note to other readers): Lots of JavaScript code ahead.. in fact, there's no PHP here. So I hope you already have some JavaScript skills prior to reading this answer. :) Thanks!

I've also got a filter in place that sets the script type of my .js file to text/babel so JSX interprets correctly. I don't think I've missed anything.

But for production usage, you should build your script or don't use the JSX syntax in your code because browsers don't understand the syntax without a compiler's help. Also, in production or a "standard"/live WordPress site, you should not use the text/babel type, hence there's no need to load/include the Babel compiler because even React itself says that the compiler makes your site slow. So use it for development and testing purposes only.

Update Mar 16 2020 (regarding above statement): No, I did not mean it's not recommended to use JSX in your code. In fact, I do recommend it because it's easier to, for example create a strong element using JSX than "the normal way" using wp.element.createElement() in Gutenberg or React.createElement() in native React apps:

  • Creating a strong element in JSX:

    <strong className="foo">
      It's easy, pretty much like regular HTML markup.
      But you can use JS expressions: 2 * 5 = { 2 * 5 }.
    </strong>
    
  • Creating a strong element "the normal way" (non-JSX):

    // In native React, you'd use React.createElement().
    // Not super hard, but how about nested elements?
    wp.element.createElement( 'strong', {
      className: 'foo'
    }, 'Some text here. 2 * 5 = ', 2 * 5 );
    

And the thing that I do not suggest is including an in-browser JSX preprocessor like Babel on your site. If that's not what you're referring to, I'm sorry, but still, what I was really saying is, if you use JSX in your script, then it's highly recommended to build the script prior to using it on a production/live site. But you can opt not to develop in JSX, if you want to or for simple stuff that does not involve complex elements.

I hope that makes sense now? (Note: I reworded the above paragraph on Apr 02 2020.)

The issues in your code

  1. The MyCheckboxControl issue is not your fault, but unfortunately, the Gutenberg team made a mistake in their example. The proper code should be:

    Update Apr 03 2020: So the code was corrected on March 17, 2020. But today, I noticed it's there again. :( So I'm no longer going to say it's fixed, even if it's already fixed. Instead, for the code in the question, the below is the proper one.

    const MyCheckboxControl = () => {
        const [ isChecked, setChecked ] = useState( true );
        return (
            <CheckboxControl
                heading="User"
                label="Is author"
                help="Is the user a author or not?"
                checked={ isChecked }
                onChange={ setChecked }
            />
        );
    };
    
  2. You can't pass JSX to wp.element.createElement():

    Update Apr 02 2020: Actually you can; just make sure it's done properly — and if you're not including any JSX preprocessor in your site, then be sure to build the script so that the JSX is transformed to the format understood by most browsers. And to @Andrew, I mainly revised this answer so that others don't get a false info because of the above statement. :)

Additional Notes (In response to your answer)

So you're right about that const { CheckboxControl } = wp.editor; //incorrect, which I did not notice because I directly used wp.components.CheckboxControl.

However, the following:

const el = wp.element.createElement;
const setState = wp.data.setState;
const withSelect = wp.data.withSelect;
const withDispatch = wp.data.withDispatch;
const { CheckboxControl } = wp.editor;
const { RichText } = wp.editor;
const { useState } = wp.element;
const { TextControl } = wp.components
const { withState } = wp.compose;

can be made simpler:

const el = wp.element.createElement;
const { setState, withSelect, withDispatch } = wp.data;
const { CheckboxControl, TextControl } = wp.components;
const { RichText } = wp.editor;
const { useState } = wp.element;
const { withState } = wp.compose;

I.e. Use const { <property>, <property>, <property>, ... } = myObject; where <property> is the property name in whatever the object is. Exception to the el since wp.element.el is undefined.

Creating React/Gutenberg components

You can actually check the React's official docs like this for creating components and working with the component's prop(ertie)s. And for examples, the following are (using JSX and) all valid:

// Using normal function.
function Welcome( props ) {
  return <h1>Hello, {props.name}</h1>;
}

// Using the class method.
class Welcome extends wp.element.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// Using "arrow" function.
const Welcome = ( props ) => <h1>Hello, {props.name}</h1>;

// No props, so here it's OK to use no function wrapping.
const Welcome = <h1>Hello <i>World</i>!</h1>;

// Note: If there are DIRECT child elements, wrap in a container like a Fragment.
const Welcome =
    <>
        <h1>Hello World!</h1>
        <p>Foo bar baz</p>
    </>
;

And for those who don't know or not yet familiar with the so-called "arrow function" (in ESNext/ES6), please check Arrow function expressions on MDN.

Accessing component properties/props

As with the function/component props which is really a function parameter/argument, whether it's React, Gutenberg, Vue, jQuery, etc. library, you just access the function parameters the same way. And sorry, I can't give a tutorial here on the different ways to access the parameters, but the MDN reference would be your best friend. Nonetheless, I hope these (very simple) examples help you:

// Log all parameters passed to the function. Similar to func_get_args() in PHP.
function foo() {
    console.log( ...arguments );
}

function foo( props ) {
    console.log( props );
}

// You should pass an object to the function.
function foo( { a, b } ) {
    console.log( { a, b } );
}

// All of the above show {a: 1, b: "two"} when you do:
foo( { a: 1, b: 'two' } );

function foo2( a, b, ...more ) {
    console.log( a, b, more );
}
// This one would show: 1 "two" (3) [3, "four", false]
foo2( 1, 'two', 3, 'four', false )

And here's an example for a class-based component, where you'd use this.props to access the component's props, although you can really use any of the above methods to access the props:

class Foo extends wp.element.Component {
    constructor() {
        super( ...arguments ); // you must call super()
        console.log( this.props );
    }
    /* You can also do so:
    constructor( props ) {
        super( ...arguments );
        console.log( props, this.props === props ); // the condition should return true
    }

    // Or like so:
    constructor( { name, place } ) { // unpack the props
        super( ...arguments );
        console.log( name, place );
    }
    */

    render() {
        const { name, place } = this.props;
        return <h1>Hello, { name } from { place }!</h1>;
    }
}

// The following JSX would output <h1>Hello, John from Pluto!</h1>
// And in the console, you'd see: {name: "John", place: "Pluto"}
//<Foo name="John" place="Pluto" />

Colophon

So I hope this (revised) answer helps you (more) and really, when it comes to Gutenberg, your best pals are the React's official site, MDN web docs for JavaScript and of course, the Gutenberg (block editor) handbook.

Happy coding!

| improve this answer | |
  • Thanks again... I'm just getting started and will post again once I've tested things out. One question, what's the point of JSX if its use is not recommended? I have access to Lynda.com and there's a course there by Morten Rand-Hendriksen on building custom Gutenberg blocks, in which he uses JSX all over the place in a production context. This doesn't make sense if it's not recommended as you suggest. – Andrew Mar 15 at 17:07
  • I've revised the answer. I hope that's more helpful. – Sally CJ Mar 16 at 2:02
  • 1
    Thanks again Sally. FYI, the Gutenberg documentation maintainers accepted my pull request so the erroneous code example is now fixed ;-). – Andrew Mar 17 at 17:06
  • 1
    Thanks for clarifying and for the additional examples. I'll dive into this next week again and hopefully come out with what I need. On the JSX usage, I understand what you meant now, and yes, for production and speed, compiling down to native js is the best way. In fact that is what Rand-Hendriksen had set up automatically in his dev stack come to think of it. In my case I may not bother because the one page where I need this is in the admin interface and will be infrequently visited. – Andrew Mar 19 at 17:34
  • 1
    No problem @Sally. The more detail we can add to this thread the better. There's really nothing else out there that pulls all of this together like we have that I can see. – Andrew Apr 2 at 16:20
1

The answer above was helpful. In the interest of others however, I thought I'd expand on it and post additional detail.

First of all it's very important to properly import the component you wish to use. I have an error in my original code above for CheckBoxControl.

const { CheckboxControl } = wp.editor; //incorrect

should be

const { CheckboxControl } = wp.component;

Sally is correct that the Gutenberg documentation is flawed. I've created a pull request with her correction and hopefully that will get fixed soon. More generally though, there are actually several correct ways of representing components as constant variables for use elsewhere in your code. Consider the following equivalent declarations.

//all three of these components work if used as <MyTestComponent />
const MyTestComponent1 = () =>
  <div>
    <h4>Howdy Doody1</h4>
  </div>
;
const MyTestComponent2= () => (
  <div>
    <h4>Howdy Doody2</h4>
  </div>
);
const MyTestComponent3= () => {
  return(
    <div>
      <h4>Howdy Doody3</h4>
    </div>
  )
};

There are only slight differences, but for a javascript/React neophyte like me, seeing an example somewhere one way and another elsewhere another way, is extremely confusing.

Lastly, and this was probably the most confusing, the following are also equivalent:

//all three of these forms work if used as <MyTestComponent />
const MyTestComponent1 = (props) =>
  <div>
    <h4>Howdy Doody1</h4>
    <p>{props.message1} {props.message2}</p>
  </div>
;

const MyTestComponent2= ({message1, message2}) => (
  <div>
    <h4>Howdy Doody2</h4>
    <p>{message1} {message2}</p>
  </div>
);

React (which Gutenberg is under a very thin disguise), uses the props object to hold properties about the component. You don't have to declare it, it's just magically there. In the first example (MyTestComponent1), the props object is explicit. However, in the 2nd example (MyTestComponent2), it is implicit and assumed. This 2nd form is much more common from what I've seen in online example code.

Said another way, in MyTestComponent2, message1, and message2 are still attributes of the props object, both forms of component declaration being equivalent... but you're just supposed to know that somehow.

Anyway... hope this helps someone else somewhere sometime.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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