I'm trying to move from procedural to object-oriented JavaScript and I'm coming up against an issue I'm sure there's an answer to, but I can't work it out.
Currently, each of my methods checks the state of a property, and then performs an action based on that state. What I'd rather do is update the state and those methods execute as a result of the state change. Is that possible, or am I missing the point?
Here's what I have currently:
class ClassExample {
constructor({state = false} = {}) {
this.state = state;
...
}
aMethod() {
if(this.state) {
//Do something
this.state = false;
} else {
//Do something else
this.state = true;
}
}
bMethod() {
if(this.state) {
//Do something
this.state = false;
} else {
//Do something else
this.state = true;
}
}
}
And:
const myObject = new ClassExample();
myObject.aMethod();
myObject.bMethod();
Given both methods are checking the same property, it's resulting in a lot of redundant if statements. Is there a better way to organise this class to achieve the same result?
I'd suggest you use an event driven system based on the EventEmitter() object built into node.js.
To keep track of state changes, you can define a setter for your state variables so that any time someone sets a new state, then your setter function will get called and it can then trigger an event that indicates the state changed. Meanwhile, anyone in your object out outside your object can register an event listener for state changes.
Here's a short example:
const EventEmitter = require('events');
class ClassExample extends EventEmitter {
constructor(state = 0) {
super();
// declare private state variable
let internalState = state;
// define setter and getter for private state variable
Object.defineProperty(this, "state", {
get: function() {
return internalState;
},
set: function(val) {
if (internalState !== val) {
internalState = val;
// send out notification
this.emit("stateChanged", val);
}
}
});
}
}
let e = new ClassExample(1);
console.log(e.state);
e.on("stateChanged", function(newVal) {
console.log("state has changed to ", newVal);
});
e.state = 3;
console.log(e.state);
Related
Simple: the computed value isn't updating when the observable it references changes.
import {observable,computed,action} from 'mobx';
export default class anObject {
// THESE WRITTEN CHARACTERISTICS ARE MANDATORY
#observable attributes = {}; // {attribute : [values]}
#observable attributeOrder = {}; // {attribute: order-index}
#observable attributeToggle = {}; // {attribute : bool}
#computed get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = attrName
);
return orderedAttributeKeys;
};
changeAttribute = (existingAttr, newAttr) => {
this.attributes[newAttr] = this.attributes[existingAttr].slice(0);
delete this.attributes[existingAttr];
this.attributeOrder[newAttr] = this.attributeOrder[existingAttr];
delete this.attributeOrder[existingAttr];
this.attributeToggle[newAttr] = this.attributeToggle[existingAttr];
delete this.attributeToggle[existingAttr];
console.log(this.orderedAttributeKeys)
};
}
After calling changeAttribute, this.orderedAttributeKeys does not return a new value. The node appears unchanged.
However, if I remove the #computed and make it a normal (non-getter) function, then for some reason this.orderedAttributeKeys does display the new values. Why is this?
EDIT: ADDED MORE INFORMATION
It updates judging by logs and debugging tools, but doesn't render on the screen (the below component has this code, but does NOT re-render). Why?
{/* ATTRIBUTES */}
<div>
<h5>Attributes</h5>
{
this.props.appStore.repo.canvas.pointerToObjectAboveInCanvas.orderedAttributeKeys.map((attr) => { return <Attribute node={node} attribute={attr} key={attr}/>})
}
</div>
pointerToObjectAboveInCanvas is a variable. It's been set to point to the object above.
The changeAttribute function in anObject is called in this pattern. It starts in the Attribute component with this method
handleAttrKeyChange = async (existingKey, newKey) => {
await this.canvas.updateNodeAttrKey(this.props.node, existingKey, newKey);
this.setState({attributeEdit: false}); // The Attribute component re-renders (we change from an Input holding the attribute prop, to a div. But the above component which calls Attribute doesn't re-render, so the attribute prop is the same
};
which calls this method in another object (this.canvas)
updateNodeAttrKey = (node, existingKey, newKey) => {
if (existingKey === newKey) { return { success: true } }
else if (newKey === "") { return { success: false, errors: [{msg: "If you'd like to delete this attribute, click on the red cross to the right!"}] } }
node.changeAttribute(existingKey, newKey);
return { success: true }
};
Why isn't the component that holds Attribute re-rendering? It's calling orderedAttributeKeys!!! Or am I asking the wrong question, and something else is the issue...
An interesting fact is this same set of calls happens for changing the attributeValue (attribute is the key in anObject's observable dictionary, attributeValue is the value), BUT it shows up (because the Attribute component re-renders and it pulls directly from the node's attribute dictionary to extract the values. Again, this is the issue, an attribute key changes but the component outside it doesn't re-render so the attribute prop doesn't change?!!!
It is because you have decorated changeAttribute with the #action decorator.
This means that all observable mutations within that function occur in a single transaction - e.g. after the console log.
If you remove the #action decorator you should see that those observables get updated on the line they are called and your console log should be as you expect it.
Further reading:
https://mobx.js.org/refguide/action.html
https://mobx.js.org/refguide/transaction.html
Try to simplify your code:
#computed
get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = this.attributes[attrName])
);
return orderedAttributeKeys;
};
#action.bound
changeAttribute(existingAttr, newAttr) {
// ...
};
Also rename your Store name, Object is reserved export default class StoreName
My object is a independent js file that I created.
componentDidMount() {
const node = ReactDOM.findDOMNode(this);
const widgetBuild = new window.WidgetFormBuilder({
form: $(node).parents('#dynamic_form_wrapper')
});
widgetBuild.initForm();
}
It is a little hard to workout without knowing more about WidgetFormBuilder.
However as good practice I would suggest...
componentDidMount() {
const node = ReactDOM.findDOMNode(this);
// Assign to the class instance
this.widgetBuild = new window.WidgetFormBuilder({
form: $(node).parents('#dynamic_form_wrapper')
});
this.widgetBuild.initForm();
}
componentWillUnmount() {
// Cleanup
// Check if WidgetFormBuilder has a destroy method or something similar.
// See https://reactjs.org/docs/react-component.html#componentwillunmount
this.widgetBuild = null;
}
shouldComponentUpdate() {
// Stop further re-renders, given you're using the DOM directly this could help prevent a few performance issues
// See https://reactjs.org/docs/react-component.html#shouldcomponentupdate
return false;
}
Finally, take a look at the react docs on third party libs.
I already fixed i just added a .destroy() function in my WidgetFormBuilder. :)
WidgetFormBuilder.prototype.destroyBuilder = function () {
const self = this;
const destroyEvents = function () {
$(self.form).unbind();
};
destroyEvents();
return this;
};
i'm struggling in what would be a good practice or better approach to communicate 'sibling classes in es6' quoted because they haven't a real parent class, by definition.
let me explain better:
class Car {
constructor(typeOfMotor){
this.motor = typeOfMotor;
this.mount();
this.addListener();
}
mount() {
// Some async logic here, and this will return true or false;
}
addListener(driver) {
// Here i want to listen this.mount method and,
// when return true, then call the ride method in the driver
// If true:
driver.ride();
}
}
class Driver {
constructor(driverName) {
this.name = driverName;
}
ride(){
console.log('Highway to hell!');
}
}
class Race {
constructor() {
this.init();
}
init() {
this.car = new Car('v8');
this.driver = new Driver('michael');
}
}
var race = new Race;
race.car.addListener(race.driver);
So basically, i have some environments where i don't need to extend classes, because i want to keep them as encapsulated as possible.
And i have this top Class (not parent because the others are not inheriting anything, though).
And the question is simple, what would be the best way to create this communication between the elements.
You can pass the Driver class instance to the Car constructor and invoke any method within this instance.
I would rethink the structure and business logic here and check what kind of responsibility each component should handle.
For example, i think it's up to the driver to decide when to drive but of course the car should signal when it is ready.
So the car shouldn't invoke driver.ride and instead just signal the driver i'm on and ready to go, and the driver should invoke the driving function.
But that's arguable of course.
Here is a running example of your code (a bit modified):
class Car {
constructor(typeOfMotor, driver) {
this.motor = typeOfMotor;
this.mounted = this.mount();
this.driver = driver;
}
mount = () => {
console.log('fetching data...');
setTimeout(() => {
this.drive()
}, 1500)
}
drive = () => {
// Here i want to listen this.mount method and,
// when return true, then call the ride method in the driver
// If true:
this.driver.ride();
}
}
class Driver {
constructor(driverName) {
this.name = driverName;
}
ride = () => {
console.log('Highway to hell!');
}
}
class Race {
constructor() {
this.init();
}
init = () => {
this.driver = new Driver('michael');
this.car = new Car('v8', this.driver);
}
}
var race = new Race();
I've got a state machine in JS which (to simplify) has an initial state that is only set before anything happens.
define(function() {
var state = 'initial',
exports = {};
exports.getState = function() {
return state;
};
exports.doSomething = function() {
state = 'newState';
};
return exports;
});
Because the state is permanent until the app is reloaded, after the first test the state will never be 'initial' again, and so I need a way to reset it.
Which is the least dirty way of doing this? Should I...
a) simply make state public but mark it as private with _?
define(function() {
var exports = {};
exports._state = 'initial'
exports.getState = function() {
return this.state;
};
exports.doSomething = function() {
this.state = 'newState';
};
return exports;
});
b) make the state writable via a function?
define(function() {
var state = 'initial',
exports = {};
exports.getState = function() {
return state;
};
exports.doSomething = function() {
state = 'newState';
};
if(window.xxTests) {
window.xxTests.Module = {
setState: function(newState) {
state = newState;
}
};
}
return exports;
});
(where xx is the app prefix and xxTests is defined only as part of the test runner)
or
c) do something else entirely I didn't think of?
Your thoughts and suggestions are appreciated.
General answer: The better way of testing a component without exposing private data is putting test code inside that component. Of course this becomes quite dirty if your language doesn't support this in a clean way (like D does).
In your case I suggest the second way (I mean your option b), because it doesn't expose private data unless explicitly needed (only during tests):
if(youAreDoingATest)
{
exports.reset=function() { state='initial'; };
}
There's not a lot of context on how you're running your tests.
Most test runners have beforeEach(), beforeAll(), afterEach(), and afterAll() methods that can be used for setup and tear-down that run as their names suggest.
To solve your specific problem I would not change your "state machine" code at but instead I would create a new state machine in the beforeEach() function so that each test gets the new function object with the state set to 'initial'.
I want to house a variable in a function. This variable will change state depending on user interaction. For example:
function plan_state(current){
if (current != ''){
state = current;
}
else {
return state;
}
}
When the document loads I call plan_state('me'), and when certain things happen I may call plan_state('loved'). The problem occurs when I run a function and want to check the current state:
alert(plan_state());
I get undefined back, but the value should be 'me' as I previously set this value on document load.
What am I doing wrong?
The function isn't stateful because the state variable is declared inside the function and therefore only exists for the lifetime of the function call. An easy solution would be to declare the variable globally, outside the function. This is bad bad bad bad.
A better approach is to use the module pattern. This is an essential pattern to learn if you're serious about javascript development. It enables state by means of internal (private variables) and exposes a number of methods or functions for changing or getting the state (like object oriented programming)
var stateModule = (function () {
var state; // Private Variable
var pub = {};// public object - returned at end of module
pub.changeState = function (newstate) {
state = newstate;
};
pub.getState = function() {
return state;
}
return pub; // expose externally
}());
so stateModule.changeState("newstate"); sets the state
and var theState = stateModule.getState(); gets the state
I believe the scope of your variable is too "low"; by defining the variable within the function, either as a parameter or explicitly as a var, it is only accessible within the function. In order to achieve what you're after, you can either implement the variable outside the scope of the function, at a more global evel (not recommened really).
However, after re-reading your question, it's slightly miss-leading. Would you not want to return state regardless of the current? I think you might be after something like so:
var state;
function plan_state(current)
{
if (current != '' && current != state)
{
state = current;
}
return state;
}
Alternative object structure:
function StateManager(state)
{
this.state = state;
this.getState = function(current)
{
if (current != '' && current != this.state)
{
this.state = current;
}
return this.state;
}
}
// usage
var myStateManager = new StateManager("initial state here");
var theState = myStateManager.getState("some other state");