Hi Keith,
You’re diving into the deep end of the ‘web development’ pool with React, Refs, Mobx, etc. So, don’t feel like you’re going a little crazy if this seems more complex than it should be. In many ways, the front-end web world is crazy, and trying to play in it the way we are definitely adds a layer or two of complexity. Perspective development is challenging even for seasoned web developers that know all the tools already, so definitely be kind and patient with yourself (and us ;)) while starting to learn this set of technologies.
It does sound like you’ve caught a bit of the gist as to what is going on: React is a ‘reactive’ UI paradigm in the sense that it attempts to only draw things to the screen when it needs to. It does this be building up a full ‘shadow dom’ structure that it manages, ultimately mirroring its’ computed structure to the ‘real’ dom.
“When it needs to” is generally when there are changes to ‘special’ values that React is watching for: React Props, React State, Context, etc . The structure of components returned from a render()
function is also tracked and rebuilt/replaced when necessary. This process is something they call reconciliation and is pretty critical in understanding how or why something shows up and/or updates/renders in ‘plain’ React.
To really be comfortable and productive in building Perspective components, one really needs to be comfortable with vanilla React at a fairly deep level. Teaching React is a little out of scope for us, and even if we wanted to, we’d probably fail to improve available guides given how fast the Web world changes, but fundamentally, it’s critical to know and understand. I’d go so far as to suggest learning react outside the bounds of Perspective, to better appreciate where it leaves off, and Ignition/Perspective begin.
To that end, I would suggest reading the React Docs from cover to cover. Working knowledge of the Advanced topics in the docs will prove very helpful. In your case, the Ref section is something worth paying special attention to, as Refs can be a bit tricky, and it sounds like you may be running into forced re-rendering. I suggest going through those React docs with some intent to internalize the content, not just to get a ‘high-level view’. Having that knowledge in your pocket will help avoid some of React’s gotchas, and there are definitely a few.
For instance, taking a look at your snippet, I see:
return (
<div
{...this.props.emit()}
ref={(mount: any) => { this.mount = mount; }}
/>
);
This makes me wonder if your component/node are re-rendering unnecessarily because you’re using a lambda function as a component prop. I’d have to verify in the case of a ref, but for most ‘standard’ props, this is problematic because the lamba syntax results in a new function being created each time render is called. That scenario is a performance concern in React because (due to how Reconciliation works) these props will always get a new function reference, so even though the body of the function is identical, react just sees a new function reference was provided. The result? Component will always get re-rendered, even if all other conditions have not changed. Don’t think that’s what you want!
Instead, you might try using the Ref callback api as shown in the React docs:
@observer
export class Viewer extends Component<ComponentProps, any> {
mount: React.Ref;
renderer: any;
constructor(props: any) {
super(props);
this.mount = React.createRef();
}
// skipped other methods...
render() {
// This is where I can reference any property changes
const componentProps = this.props.props.read('PropertyName');
// This is where I can am reanimating my component based off the new properties
this.animate(componentProps);
//This is where I am supplying the renderer to the mount, so that I can see it in perspective
return (
<div
{...this.props.emit()}
ref={this.mount}
/>
);
}
}
I’m not familiar with the specific element/library you’re trying to work with, but think that’s probably more in the direction of what you want in order to avoid inappropriate redrawing.
In the context of Perspective, we effectively provide the ‘state/props’ to your ‘top level’ component (the one you register with the ComponentRegistry via the ComponentMeta). We do this through a few different ‘scopes’, which are simply named groups of properties that we (Ignition/Perspective) feed into your registered component.
That we use Mob-X really isn’t something that should concern you, but unfortunately it’s leaked a bit into our API. We plan on correcting this with a future update, but until then, the main things to note are:
- PropertyTree (the state object you get when calling
this.props.props
) uses mobx internally, so any PropertyTree.read()
in your render function will trigger a re-render if there is a value change. Understanding what mobx reacts to can be very important.
a. similarly, PropertyTree.write()
will write a value to a prop node, and trigger a corresponding reaction/re-render if the value is being tracked by a component
- Perspective Component classes (the thing you register with ComponentMeta) need to be mobx-react observables, so that they can respond to ‘tracked’ values
a. In general, any mobx value that is ‘dereferenced/read’ in an observable component’s render() method is tracked
a. Under the hood, ‘observable’ components have what is effectively an ‘automatic’ implementation of something like a shouldComponentReRender()
(not a real thing, just for educational purposes)
- You do not need to use mobx for any of your component’s internal State management unless you want to. The use of React State and/or React Props on all ‘children’ of your top-level component is not only possible, but recommended by us.
Lastly, you’ll want to be careful/aware of function scope binding in the Javascript world, as there are times where this
may not be what you think/want it to be, so you’ll want to bind some method. This is important to remember in react component classes. We use a decorator for this @bind
, but it’s just syntactic sugar that applies the correct function scoping in the same way a constructor binding does:
class SomeThing {
instanceValue = "";
constructor() {
this.someMethod = this.someMethod.bind(this);
}
someMethod(anArg) {
this.instanceValue = anArg; // without binding, there is no guarantee 'this' is my SomeClass object!
}
}
Hopefully this helps. Feel free to create a new post if you have specific questions.
-Perry