I'm migrating my front-end code for a plugin to React and ESNext, and am still new to all of this, but for some reason, this really simple for
loop isn't executing on the admin edit page, but is fine when viewing the post. The only thing I can see is that there seems to be a weird issue with how HTMLCollections are counted, on the back end. I have this code:
EDIT: added the php back-end code that generates the div
in question, and appended the full JS edit function.
/*
* external dependencies
*/
import * as React from "react";
import { render } from "react-dom";
export default class App extends React.Component {
render() {
return (
<div className={"myapp-container"}>
// this is a test
</div>
);
}
}
document.addEventListener("DOMContentLoaded", (event) => {
let els = document.getElementsByClassName("myapp_wrapper_class");
console.log("got elements ", els); // this fires fine, everywhere
for (let el of els) {
console.log("working with ", el); // this fires on the front-end, but not in the editor
render(<App />, document.getElementById(el.id));
}
});
When I look at the console, on the front end, it logs
got elements
HTMLCollection (1) // note this "1"
0 <div id="myApp1" style="width: 100%; height: 300px;" class="myapp myapp_wrapper_class" data-cwragc-src="http://localhost:8888/wp-content/uploads/2021/01/nakaza.csv" data-cwragc-type="line"></div>
but in the editor, it logs
got elements
HTMLCollection (0) // note this "0"
0 <div id="myApp1" style="width: 100%; height: 300px;" class="myapp myapp_wrapper_class" data-cwragc-src="http://localhost:8888/wp-content/uploads/2021/01/nakaza.csv" data-cwragc-type="line"></div>
I'm unsure whether that zero is the problem, but like I said, it's the only thing I can see that's weird. There are no error messages in the console. I am sure that the for
loop isn't being executed on the back end, even though the element is there (as you can see in the console log), and exists in the rendered DOM.
The elements are created with a server-side render. In the block's edit function, it looks thusly (full source at end of post):
export function MyAppEdit( props ) {
//...
const MyAppRender = () => {
return(
<Disabled>
<ServerSideRender
block={ props.name }
attributes={{ ...attributes }} />
</Disabled>
);
}
//...
return (
<>
{ controls }
<div { ...blockProps }>
{ cwragcLocalFile && <MyAppRender /> }
{ myAppPlaceholder }
</div>
</>
);
}
export default withNotices( MyAppEdit );
Grasping at straws, I also tried removing the <Disabled>
wrapper on the editor side, but no change in effect.
The div
in questions is rendered by the server-side render, which on the back end looks like this:
// gutenberg editor specific functionality
$this->loader->add_action( 'init',
$plugin_admin, 'enqueue_gutenberg_scripts' );
// in $plugin_admin
public function enqueue_gutenberg_scripts() {
$asset_file = include( plugin_dir_path( __FILE__ )
. '../assets/js/myAppAdmin.asset.php');
// register the compiled JS for the backend
wp_register_script( $this->plugin_name . '-block-edit',
plugin_dir_url( __FILE__ )
. '../assets/js/myAppAdmin.js',
$asset_file['dependencies'],
$this->date_version(
'../assets/js/myAppAdmin.js')); //adds date stamp to version so we don't run into cacheing problems during development
register_block_type( 'cwra-block/myblock',
array(
'attributes' => array(
'cwragcBaseId' => array(
'type' => 'string',
'default' => 'myApp1'
),
'cwragcLocalFile' => array (
'type' => 'string'
),
'cwragcType' => array(
'type' => 'string',
'default' => 'line'
),
'cwragcWidth' => array (
'type' => 'number'
)
),
'editor_script' => $this->plugin_name . '-block-edit',
'render_callback' => array( $this->plugin_public,
'render')
) );
}
// now in $this->plugin_public
public function render( $block_attributes, $content = '' ) {
// if we don't have data, we can't do anything
if ( ! array_key_exists('cwragcLocalFile',
$block_attributes) ) {
return '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '_control_div" style="width: 100%; '
. 'min-height: 50px;" class="cwragc"></div>';
}
$controlEl = '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '_control_div" style="width: 100%; min-height: 50px;"'
. ' class="cwragc cwragc_control '
. ' cwragc_column_selector">'
. '</div>';
$mainEl = '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '" style="width: 100%; height: 300px;" ';
$mainEl .= 'class="myapp myapp_wrapper_class"'
. ' data-cwragc-src="'
. print_r($block_attributes["cwragcLocalFile"], true)
. '" data-cwragc-type="'
. print_r($block_attributes["cwragcType"], true)
. '"';
$mainEl .= '></div>';
$colSelectEl = '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '_col_control_div" style="min-height: 50px;"'
. ' class="cwragc cwragc_control cwragc_col_control'
. ' form-group">'
. '</div>';
$rangeSliderEl = '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '_range_control_div" style="min-height: 50px; display: none;"'
. ' class="cwragc cwragc_control '
. 'cwragc_range_control form-group">'
. '</div>';
$el = '<div id="'
. print_r($block_attributes["cwragcBaseId"], true)
. '_dashboard_div"'
. ' class="cwragc cwragc_dashboard">'
. '<div class="outer-wrapper">'
. '<div class="app-body">'
. '<div>'
. '<div>' . $colSelectEl . $rangeSliderEl . '</div>'
. '</div>'
. '<div>'
. '<div>'
. $mainEl // THIS IS THE DIV IN QUESTION
. '</div>'
. '</div>'
. '<div>'
. '<div>'
. $controlEl
. '</div>'
. '</div>'
. '</div>'
. '</div>'
. '</div>';
$wrapper_attributes = get_block_wrapper_attributes();
$el = sprintf('<figure %1$s>%2$s</figure>',
$wrapper_attributes,
$el);
return $el;
}
Basically, I have no idea what's going on here, but I'd like my stuff to render on the back end :)
Is it possibly a Babel bug? Is there some difference/timing/race issue with when DOMContentLoaded
fires on the back end? Is there some better/more naturally React way for me to add a component to a div
when I don't know how many of them there will be (other than getElementsByClassName
after DOMContentLoaded
?
For completeness' sake, my webpack is this:
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const path = require('path');
module.exports = {
...defaultConfig,
entry: {
myAppAdmin:
path.resolve( process.cwd(), 'admin/src', 'index.js' ),
myAppPublic:
path.resolve( process.cwd(), 'public/src', 'index.js' ),
},
output: {
filename: '[name].js',
path: path.resolve( process.cwd(), 'assets/js' ),
},
}
Maybe I'm missing some other require
? But, like I said, it's not generating any errors. admin/src/index.js
is the block code; public/src/index.js
is front-end code I'm trying to migrate, that adds React stuff after the server-side render.
full edit source
/**
* WordPress dependencies
*/
import { isBlobURL } from '@wordpress/blob';
import { MediaPlaceholder, useBlockProps } from '@wordpress/block-editor';
import { Disabled, withNotices} from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import { useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import ServerSideRender from '@wordpress/server-side-render';
export function MyAppEdit( props ) {
const {
attributes: {
cwragcBaseId,
cwragcLocalFile
},
attributes,
clientId,
noticeOperations,
noticeUI,
setAttributes,
} = props;
const ref = useRef();
const { createErrorNotice } = useDispatch( 'core/notices' );
// save the clientId as an attribute so we can use it on public side
if ( ! cwragcBaseId ) {
console.log('cwragcBaseId is ', cwragcBaseId);
console.log('clientId is ', clientId);
setAttributes( { cwragcBaseId: clientId } );
}
const MyAppRender = () => {
return(
<Disabled>
<ServerSideRender
block={ props.name }
attributes={{ ...attributes }} />
</Disabled>
);
}
function onSelectSrc( data ) {
if ( ! data || ! data.url ) {
setAttributes( { cwragcLocalFile: undefined });
return;
}
if ( data.id || ! isBlobURL( data.url ) ) {
setAttributes( { cwragcLocalFile: data.url });
console.log('Set local file to ', data.url);
return;
}
console.log('Got file, ', data);
}
function onUploadError( message ) {
noticeOperations.removeAllNotices();
noticeOperations.createErrorNotice( message );
}
const chartPlaceholder = (
<MediaPlaceholder
accept={ '.csv, .json' }
allowedTypes= { [ 'text/csv', 'application/json' ] }
icon={ 'chart-bar' }
labels={ {
title: "My Block",
instructions: __( 'Upload a data file, get one from a URL, '
+ 'or schedule retrieval from a URL.', 'cwragc') }}
notices={ noticeUI }
onError={ onUploadError }
onSelect={ onSelectSrc }
disableMediaButtons={ cwragcLocalFile }
/>
);
const controls = (<></>);
const blockProps = useBlockProps( {
ref,
className: "cwragc-block"
} );
return (
<>
{ controls }
<div { ...blockProps }>
{ cwragcLocalFile && <MyAppRender /> }
{ chartPlaceholder }
</div>
</>
);
}
export default withNotices( MyAppEdit );