How to use a dojo widget inside a react component? - javascript

Is there any way to re-use a component/widget from other library inside a react component?
E.g.
export default function App() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>{count}</button>
<button id="btn" onClick={() => function(){
require(["dijit/form/Button", "dojo/domReady!"], function(Button) {
var button = new Button({
label: "Click Me!",
onClick: function(){ console.log("Opening a dojo dialog"); }
}, "btn");
button.startup();
});
}}>Open Dojo component</button>
<br />
</div>
);
Here, this is a basic react component. I am trying to use a dojo button inside it but apparently does not work. I am actually thinking in the lines of having a button event in the react and call some javascript function which parses the dojo widget. Not sure if that will work.
Is there a better approach?

The reason your attempt didn't work is because you're still rendering plain buttons in the React JSX, and when you click that button you're creating a new Dojo button but you're not adding it to the DOM. Even if you did add it to the DOM, React might still remove it on a later re-render because it's not part of the JSX.
We've recently undergone an integration of React into an existing Dojo application, and the method we use to put Dojo widgets into React components is as follows.
The high level overview is that we create special React components that "host" Dojo widgets inside them. I'll start from the top and work my way down.
The usage of these hosted Dojo widgets is mostly straight forward, and looks something like the following JSX
<div>
<Button
dojoProps={{ label: "Add" }}
dojoEvents={{ click: onAdd }}
/>
<Button
dojoProps={{ label: "Remove" }}
dojoEvents={{ click: onRemove }}
/>
</div>
where Button is our Dojo hosting component. The entries in the dojoProps property are passed into the constructor of the widget and into .set(...) when the properties change. Then entries in dojoEvents are passed into .on(...) when being created and when they change as well.
The Button class looks like this (it's in TS, but should be easy to translate into JS)
import * as DojoButton from "dijit/form/Button";
import { DojoInReactComponent } from "../DojoInReactComponent";
export class Button extends DojoInReactComponent<DojoButton> {
constructor(props: Button["props"]) {
super(new DojoButton(props.dojoProps), props);
}
}
We make one of these classes for each Dojo widget we wish to wrap and show in React and is re-usable throughout the project. Note that this is where the widget is created and the props are passed into the widget's constructor.
The important part of the implementation is in the DojoInReactComponent class:
import * as React from "react";
import * as _WidgetBase from "dijit/_WidgetBase";
/**
* A React component that hosts a Dojo widget
*/
export abstract class DojoInReactComponent
<W extends _WidgetBase, P extends DojoInReactComponentProps<W> = DojoInReactComponentProps<W>>
extends React.Component<P> {
/** Stores a React Ref to the actual DOMNode that we place the widget at */
private readonly widgetRef: React.RefObject<HTMLSpanElement>;
/** Cache of the registered event handles for this widget (used to cleanup handles on unmount) */
private readonly eventsRegistered: EventRegistrationCache<W> = {};
/** The actual widget that will be stored in this component */
readonly widget: W;
constructor(widget: W, props: P) {
super(props);
this.widgetRef = React.createRef();
this.widget = widget;
}
componentDidMount() {
if (!this.widgetRef.current) {
throw new Error("this.widgetRef was not set");
}
// First, set properties
this.widget.set(this.props.dojoProps ?? {});
// Then set event handlers. This is the first time it happens so
// the eventsRegistered cache is empty (ie: nothing to remove).
this.addEventHandlers(this.props.dojoEvents);
// Finally, place it at the domNode that this component created when it rendered.
this.widget.placeAt(this.widgetRef.current);
}
componentDidUpdate(prevProps: P) {
// First, update props. Note that we merge the old and new properties together here before setting to
// ensure nothing drastically changes if we don't pass in a property. If we want it to change, we need to
// explicitly pass it in to the dojoProps property in the TSX.
// This also attempts to make it obvious that not setting a property in the TSX will leave it unchanged,
// compared to it's existing value, as that's the default behaviour of Dojo widgets and .set().
const props = { ...prevProps.dojoProps ?? {}, ...this.props.dojoProps ?? {} };
this.widget.set(props);
// Then update event handlers. Note that we don't do this in a "smart" way, but instead we just remove all
// existing handlers, and then re-add the supplied ones. Generally it will mean removing and re-adding the same
// handlers, but it's much easier than trying to diff them.
this.removeAllEventHandlers();
this.addEventHandlers(this.props.dojoEvents);
}
componentWillUnmount() {
// On cleanup we need to remove our handlers that we set up to ensure no memory leaks.
this.removeAllEventHandlers();
// Finally we destroy the widget
this.widget.destroyRecursive();
}
private addEventHandlers(events: EventsType<W> | undefined) {
if (!events) {
return;
}
for (const key of keysOf(events)) {
const newHandle = this.widget.on(key as any, events[key] as any);
this.eventsRegistered[key] = newHandle;
}
}
private removeAllEventHandlers() {
for (const key of keysOf(this.eventsRegistered)) {
const handle = this.eventsRegistered[key];
if (handle) {
handle.remove();
}
delete this.eventsRegistered[key];
}
}
render() {
return <span ref={this.widgetRef}></span>;
}
}
function keysOf<T extends {}>(obj: T) {
return Object.keys(obj) as Array<keyof T>;
}
type EventsType<T extends _WidgetBase> = Record<string, () => void>;
type PropsType<T extends _WidgetBase> = Partial<T>;
export interface DojoInReactComponentProps<W extends _WidgetBase> {
dojoProps?: PropsType<W>;
dojoEvents?: EventsType<W>;
}
type EventRegistrationCache<W extends _WidgetBase> = Record<string, dojo.Handle>;
There's a bit to unpack here, but the general gist of it is:
render() produces a simple <span> element that we keep a reference to to mount our Dojo widget to shortly
componentDidMount() updates the properties and event handlers of the Dojo widget to our supplied properties. It then places the actual Dojo widget into the DOM produced by the React component
componentDidUpdate() again updates the properties and event handlers if they changes due to a re-render of the React component
componentWillUnmount() is called when the React component is being removed and does a teardown of the hosted Dojo widget by calling .destroyRecursive() to remove it from the DOM
The types down the bottom are just utility types to help TS infer everything for us, however EventsType and EventsRegistrationCache uses additional changes that are far too long to write out here, so I've replaced them with a stub (but if you're just using JS then it won't matter).
Let me know if you have any questions about it.

Related

Reactjs render and state change dependancies

I'm learning ReactJS at the moment and struggling to understand how to render/update content based on changes elsewhere.
Example:
I have a timer app, which includes pause/restart functionality. It contains a Start/Pause button.
Timer.js
export class Timer {
constructor(parentApp) {
this.app = app;
this.playing = false;
}
start() {
this.playing = true;
}
pause() {
this.playing = false;
}
}
Button.js
export class IconButtonBar extends Component {
constructor(props) {
super(props);
this.state = {label: 'Start'};
}
render() {
return (
<div className="IconButtonBar">
<Button label={this.state.label} />
</div>
);
}
}
I want to update the label:
Depending whether the timer is started/stopped at initial render
When the timer is manually started/stopped by pressing the button
When the timer is manually started/stopped by another means
When the timer is programatically started/stopped (e.g. limit reached)
In jQuery I'd probably fire a custom event trgger to the body tag:
$('body').on('start_playing', function() {
$('#playpause_button).text('Pause');
}
But there are probably much more 'native' ways to do this in ReactJS.
I hope this makes sense, and you can help!
You need to change your vision of how react app is structurized. Just forget about jQuery. React is declarative, you need to say it what should be rendered (surely it can be done another way, but why to use React then?).
You can use stateful component if you have no third-party store library.
You can use conditional redering for showing different elements depending on different state.
Here is an example stateful component of what you wanted to achieve: https://codesandbox.io/s/14vokm9q14
Just remember, it is not a good practice to keep state in a view layer. Consider using Redux/Mobx:
Redux - it will require a lot of boilerplate: creation of reducers, action-creators, action-types, handling side-effects (with redux-thunk/redux-saga/redux-observable), etc. But it is stable, reliable and easy-testable.
Mobx - something like you have in your example. It is MVVM, where you have model in its classical meaning, decorate properties as observable and just inject this model into your react component. After that you can just mutate properties of your model and these changes will reflect onto your view.

Ag-grid plain JS cell renderer in React

I am using ag-grid in a React application. We have pretty heavy duty tables with custom cells. There are performance issues when using custom cell renderers built as React components, which is the most intuitive discourse.
The ag-grid docs state that this is probably not a good idea:
Do NOT use a framework (eg Angular or React) for the cell renderers. The grid rendering is highly customised and plain JavaScript cell renderers will work faster than framework equivalents.
But it also indicates that plain JS can be used in conjunction with frameworks like React:
It is still fine to use the framework version of ag-Grid (eg for setting ag-Grid properties etc) however because there are so many cells getting created and destroyed, the additional layer the frameworks add do not help performance and should be provided if you are having performance concerns.
Am I misinterpreting this? This seems to me that I can use just a plain JS class as a cell renderer (somehow, maybe they'll handle the integration with React?)
So I took their example code and converted it to a class instead of a function to conform to their typescript definitions:
// function to act as a class
class MyCellRenderer {
eGui: any;
eButton: any;
eValue: any;
eventListener: any;
init(params: any) {
// create the cell
this.eGui = document.createElement('div');
this.eGui.innerHTML =
'<span class="my-css-class"><button class="btn-simple">Push Me</button><span class="my-value"></span></span>';
// get references to the elements we want
this.eButton = this.eGui.querySelector('.btn-simple');
this.eValue = this.eGui.querySelector('.my-value');
// set value into cell
this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
// add event listener to button
this.eventListener = function() {
// tslint:disable-next-line
console.log('button was clicked!!');
};
this.eButton.addEventListener('click', this.eventListener);
}
// gets called once when grid ready to insert the element
getGui() {
return this.eGui;
}
// gets called whenever the user gets the cell to refresh
refresh(params: any) {
// set value into cell again
this.eValue.innerHTML = params.valueFormatted ? params.valueFormatted : params.value;
// return true to tell the grid we refreshed successfully
return true;
}
// gets called when the cell is removed from the grid
destroy() {
// do cleanup, remove event listener from button
this.eButton.removeEventListener('click', this.eventListener);
}
}
// gets called once before the renderer is used
export default MyCellRenderer;
This builds just fine. Now when I pull up my table in the app, I get the somewhat predictable error:
MyCellRenderer(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
So it was expecting a React component exclusively? It appears that I need to provide the rendering operation anyway.
Does anyone know what's going on here/how to resolve this issue? Am I misinterpreting the documentation?
Thanks!
p.s. ag-grid is awesome!
Have just worked out how to do it. There's two sets of 'grid components' properties that you can make available for an instance of a grid for renderers and editors.
The frameworkComponents property contains ones that will be rendered using the framework you're using, such as React or Angular.
The components property contains ones that will be rendered using straight JS.
You can mix these however you wish, for example, assuming you have some renderers that have been exported using this pattern:
export const XRenderer = {
id: 'someId',
renderer: function() ... // or class, or whatever
}
// React components
const frameworkComponents = {
[CheckboxRenderer.id]: CheckboxRenderer.renderer,
[SelectRenderer.id]: SelectRenderer.renderer
};
// JavaScript components
const components = {
[RateRenderer.id]: RateRenderer.renderer
};
<Grid
columnDefs={columnDefinitions}
theme={theme}
rowData={rows}
frameworkComponents={gridComponents} // React components
components={components} // JavaScript components
onGridReady={this.onGridReady}
context={this.gridContext}
gridOptions={this.mainGridOptions}
...
/>
The information on how to do this is actually in the docs, but not in a particularly useful place: https://www.ag-grid.com/javascript-grid-components/#mixing-javascript-and-framework

Closures in React

Is it ok use closures in react, for event handlers?
For example, i have some function and a lot of menu in navigation
and in navigation component i use something like this:
handleMenuClick(path) {
return () => router.goTo(path)
}
...
<MenuItem
handleTouchTap={this.handleMenuClick('/home')}
>
or i should prefer just arrow function?
<MenuItem
handleTouchTap={() => router.goTo('/home')}
>
first variant really make code cleaner, but i'm worried about performance with a large number of such elements
Both should be avoided.
While they'll both work, they both have the same weakness that they'll cause unnecessary renders because the function is being created dynamically, and will thus present as a different object.
Instead of either of those, you want to create your functions in a static way and then pass them in. For something like your MenuItem, it should just get the string for the path and then have the code to do the routing inside. If it needs the router, you should pass that in instead.
The function should then be a pre-bind-ed function (usually in the constructor) and just passed in.
export class MenuItem extends React.Component {
constructor() {
this.handleClick = () => this.props.router.go(this.props.path);
}
render() {
return (
<Button onClick={ this.handleClick }>Go to link</Button>
);
}
}
You can use an arrow function in the constructor. That way it isn't recreated every render function, and thus you avoid unnecessary renders. That pattern works well for single-line simple functions. For more complex functions, you can also create them as a separate function, then bind it in the constructor.
export class MenuItem extends React.Component {
handleClick() {
this.props.router.go(this.props.path);
}
constructor() {
this.handleClick = this.handleClick.bind(this);
}
render() { /* same as above */ }
}
The point of this is that the handler is the same function every time. If it was different (which both methods you describe above would be), then React would do unnecessary re-renders of the object because it would be a different function every time.
Here are two articles which go into more details:
https://ryanfunduk.com/articles/never-bind-in-render/
https://daveceddia.com/avoid-bind-when-passing-props/
when you define a new method inside a react component (Object) as we know functions are object in javascript.
let reactComponent={
addition: function(){ //some task ...},
render: function(){},
componentWillMount : function(){},
}
so, every new method should be bind with in the object using bind, but render() is already defined so we don't do
this.render = this.render.bind(this)
for each new function, except react lifecycle methods are needed to be added and hence, we call the object (constructor function) methods using this.method().

React: How to check the type of a child in the parent's render function?

I know that during render I can get the child through refs and, for example, call a function on the child (which I can add to the child for this purpose) to determine the type of the child.
<Child ref={(child) => {this._childType = child.myFunctionToGetTheType();}} />
But in this example the function isn't actually called until the Child is mounted, so after the render of the Parent has finished executing.
I have a parent component that receives its children through props. Because of React limitations I need to treat a specific child in a special way BEFORE the render of the parent has finished executing (i.e. return something else from the parent's render function for that specific child).
Is it possible to determine the type of the child before returning from the parent's render function (i.e. without using refs)?
I've had the same issue, where I was relying on child.type.name to determine the type of component. While this works fine for me, the issue is that older browsers somehow do not support that so I had to find another way. I was using Stateless Functional Components and did not want to switch away, so ended up exploiting props
const MySFC = () => {
//...
return (
<div className="MySFC"></div>
);
};
MySFC.propTypes = {
type: PropTypes.string
};
MySFC.defaultProps = {
type: "MySFC"
}
export default MySFC;
then instead of using child.type.name === 'MySFC' I used child.props.type === 'MySFC'
not ideal, but works
I have added a static function to the class that extends React.Element and it seems that I am able to access it through child.type.theStaticFunction. That probably still isn't using the React API correctly but at least it works after the code has been minified (child.type.name didn't work because the minifier was replacing class names with shorter versions).
export default class MyType extends React.Component {
static isMyType() {
return true;
}
}
then when processing the children in render
static _isChildOfMyType(child) {
const isMyType = child.type && child.type.isMyType && elem.type.isMyType();
return !!isMyType;
}

Is it possible to create children nodes that provide configuration to the parent component?

The scenario where I am looking to do this is to create a generic datagrid component. I would like to use the children to define the columns of the grid, but I don't want those children to render because rendering logic is abstracted to a presentation of the grid structure (the view property below)
i.e.,
<MyDataGrid data={myData} view={myTableView}>
<MyCol fieldName='asdf' sortable />
</MyDataGrid>
Because the columns provide rendering information to the grid, they need to be accessible in the grid's render function without having first rendered themselves. From what I can tell this is not possible and I am currently just passing the column configuration as a prop on the grid. This strategy works fine but is certainly not the best looking strategy.
I know you can build instrinsic elements that can be used, but I think React still wants to manage them as DOM nodes. What i want is for react to ignore the DOM for the children of my component and let me just parse the children verbatim (i.e. as an array of MyCol { fieldName: string, sortable: boolean }).
Is this possible? scheduled on the road map? even considered?
I know it is a bit of a strange question, and I'm happy to continue with the strategy I've employed so far. But it would be nice to have the option to create these renderless "dumb" nodes.
Sure! Take a look at React Router for an example of this style of configuration. That said, I think it works better for things that are nested (like route configs); otherwise, I'd recommend just using the more JavaScript-centric style of defining an object or array of objects to configure the grid.
In your example, MyCol doesn't need to be rendered, you just want to introspect the properties it was created with. Here's an example:
var MyDataGrid = React.createClass({
render: function() {
var options = React.Children.map(this.props.children, (child) => {
return {
type: child.type.displayName,
sortable: !!child.props.sortable,
fieldName: child.props.fieldName
};
});
return <pre>{JSON.stringify(options, null, " ")}</pre>
}
});
var MyCol = React.createClass({
render: function() {
return null;
}
});
var app = (
<MyDataGrid>
<MyCol fieldName='asdf' sortable />
<MyCol fieldName='anotherColumn' />
</MyDataGrid>
);
ReactDOM.render(app, document.getElementById("app"));
Example: https://jsbin.com/totusu/edit?js,output
The main problem with processing children for configuration is that your react component class will not have it's constructor called when you receive the children props in your parent component. This makes things complicated if you have any logic associated with the configuration node/component that needs to be applied.
The pattern I am now using is as follows (and is confirmed to be working as you would expect). I am using ES6 classes for components but the pattern is applicable to the functional React.create* style as well.
Create the props interface (this defines which props can be used in your configuration child nodes)
Create a dummy component class that makes use of the props you created (this component is literally just a definition and contains no code)
Create a configuration class with a constructor that consumes all props and performs any initialization logic.
Add a static create function to your configuration class that consumes a ReactElement and returns a new instance of your configuration class (the element will have the props defined in jsx/tsx
Use React.Children.map to convert the children props into a configuration instance array in your parent (this can be done in componentWillMount and saved to an instance variable, unless your column definitions are mutable).
Simplified Example (in Typescript)
interface IDataGridColumnProps {
fieldName: string;
header?: string;
sortable?: boolean;
}
export class DataGridColumn extends React.Component<IDataGridColumnProps, any> {}
class Column {
constructor(public fieldName: string, public header?: string, public sortable = false) {
if (this.header == null) {
this.header = this.fieldName;
}
}
public static create(element: React.ReactElement<IDataGridColumnProps>) {
return new Column(element.props.fieldName, element.props.header, element.props.sortable);
}
}
export class DataGridView /* ... */ {
private columns: Column[];
componentWillMount() {
this.columns = React.Children.map(this.props.children, (x: React.ReactElement<IDataGridColumnProps>) => {
return Column.create(x);
});
}
}
The pattern used above is to essentially convert the component nodes into configuration instances on the parent component. The original children nodes are thrown out and the configuration they provide is retained in an instance array that lives within the parent component.

Categories

Resources