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 = wppose.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 } = wpponents
const { withState } = wppose;
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...
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?
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 = wppose.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 } = wpponents
const { withState } = wppose;
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...
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?
Share Improve this question asked Mar 15, 2020 at 3:27 AndrewAndrew 1992 silver badges10 bronze badges 1- 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 Commented Mar 15, 2020 at 4:08
2 Answers
Reset to default 2Warning (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 totext/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" usingwp.element.createElement()
in Gutenberg orReact.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
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 } /> ); };
You can't pass JSX towp.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 wpponents.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 } = wpponents
const { withState } = wppose;
can be made simpler:
const el = wp.element.createElement;
const { setState, withSelect, withDispatch } = wp.data;
const { CheckboxControl, TextControl } = wpponents;
const { RichText } = wp.editor;
const { useState } = wp.element;
const { withState } = wppose;
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!
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 } = wpponent;
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.