ES6 circular dependency - javascript

This is an issue I run into fairly frequently, and I was hoping to discover the correct way to handle it.
So I have a setup like this:
parent.js:
export default {
x: 1
}
a.js:
import parent from 'parent.js'
export default parent.extend(a, { title: 'a' })
b.js:
import parent from 'parent.js'
export default parent.extend(b, { title: 'b' })
Cool, now I've got some children.
But I decide I would like to have a function in parent.js that checks if an object is an instance of a or b.
So I might do this:
parent.js:
import a from 'a'
import b from 'b'
export default {
x: 1,
checkType (obj) {
if (obj instanceof a) {
return 'a'
} else if (obj instanceof b) {
return 'b'
}
}
}
Well now that's a circular dependency. Is there an elegant way to handle this?

Having logic in the parent class that is aware of the subclasses is a serious anti-pattern. Instead, add methods in the subclasses that return the type. for instance, in a.js:
import parent from 'parent.js';
export default parent.extend(a, { title: 'a', checkObj() { return 'a'; }});
If the desired return from checkObj is always the value of the title property, then of course just:
// parent.js
export default {
x: 1,
checkObj() { return this.title; }
}
I don't know exactly what extend is doing here. I'm assuming it is some kind of subclassing mechanism.
In general, circular import dependencies, although there are ways to deal with them when truly necessary, are the universe trying to tell you that there is something wrong with the way you've structured your code.

If you're able to use es6 classes, then you can take advantage of the super() call in the constructor. I'll often do something like this:
Parent.js
export default class {
constructor(options, child) {
this.child = child;
this.x = 1;
}
checkType() {
return this.child;
}
}
A.js
import Parent from './Parent';
export default class extends Parent {
constructor(options) {
super(options, 'a');
}
}
B.js
import Parent from './Parent';
export default class extends Parent {
constructor(options) {
super(options, 'b');
}
}
If you don't want to use classes, maybe want a more FP style. You could make parent a function:
parent.js
export default function(child) {
return {
x: 1,
checkType (obj) {
return child;
}
extend (something) {
// assuming the returns something as you said
}
}
}
a.js
import parent from 'parent.js'
export default parent('a').extend(a, { title: 'a' })
b.js
import parent from 'parent.js'
export default parent('b').extend(b, { title: 'b' })

Related

Keep my code DRY for `$onInit` and `$onChanges` methods

I have a piece of code that is repeated in almost all of my components:
import {deviceCommands} from "../../../core/data/Commands";
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export default class GeneralDeviceSettingsCtrl {
constructor($scope) {
let $ctrl = this;
$ctrl.$onChanges = function (changes) {
for(let prop in _generalProperties) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
};
$ctrl.$onInit = function () {
for(let prop in _generalProperties) {
$scope.$watch(() => $ctrl.test.vm.data[_generalProperties[prop]],
(newValue, oldValue) => {
if (newValue !== oldValue) {
$ctrl[prop] = $ctrl.test.vm.data[_generalProperties[prop]];
}
});
}
};
}
}
The only thing that is different is the _generalProperties variable, which is specific to my view.
How can I do to keep it DRY?
Make a base class? use decorators?
I think there is plenty of different approaches to this but if you stick with classes and inheritance you can supply the _generalProperties to your parent constructor and reuse the whole implementation.
For example:
export class BaseSettingsComponent {
constructor(properties){
this._properties = properties;
}
$onInit(){ /* ... implement base component stuff*/ }
}
Then, on each of your pages that uses the same component logic you can extend the base class, provide the properties to the parent class and let the base class do the job.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
let _generalProperties = {
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
};
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super(_generalProperties);
}
$onInit(){ super.$onInit(); /* ... implement base component stuff*/ }
}
The one thing important to keep in mind when using this approach is that if you need to implement $onInit or $onChanges on the child classes you have to call the super.$onInit() otherwise you lose the parent behavior due to an override.
Finally, for an even cleaner code, you can also discard the declaration of _generalProperties supplying it directly into the super constructor.
import { BaseSettingsComponent } from '../common/base-settings-component.js';
export class GeneralDeviceSettingsCtrl extends BaseSettingsComponent {
constructor(){
super({
'deviceName': deviceCommands.NAME.key,
'deviceModel': deviceCommands.MODEL.key
});
}
}

With React, is it possible to have a propType to specify the DOM element type?

I would like to create a React UI-Compoment for Typography. Where the component can be passed a propType to specify which element type should be rendered or
Perhaps a propType used like: as="span" or as="p"
Is this type of dynamic element type definition possible with React or do I need to do something less elegant and have a Switch statement with two types or return statements?
example: `if (this.props.p) {return <p>....</p>}
Example using this proposed component:
<MyC as="span">Hello World</MyC>
<MyC as="p">Hello World</MyC>
You could use React.createElement() as such:
class MyC extends Component {
state = {
ready: false,
};
componentDidMount() {
this.el = React.createElement(this.props.as, {}, this.props.children);
this.setState({ ready: true });
}
render() {
if (!this.state.ready) {
return null;
}
return this.el;
}
}
Usage:
class App extends Component {
render() {
return <MyC as="span">Test</MyC>;
}
}
Working example: https://codesandbox.io/s/xozpn8ol9o

Correct way to export object in es6 module

I'm trying to export an my module as an object but exporting it seems an 'anti pattern' (https://medium.com/#rauschma/note-that-default-exporting-objects-is-usually-an-anti-pattern-if-you-want-to-export-the-cf674423ac38)
So I was wondering what's the correct way to export an object and then use it as
import utils from "./utils"
`
utils.foo()
Currently I'm doing like so
/** a.js **/
function foo(){
//...
}
export {
foo
}
/** b.js **/
import * as utils from "a";
utils.foo()
is it correct like so? Do I maintain the tree-shaking feature?
thanks
If the object you want to import/export only contains some functions (as I assume due to the Utils name), you can export the functions separately as follows:
export function doStuff1() {}
export function doStuff2() {}
And import like this:
import {doStuff1, doStuff2} from "./myModule";
However, if the object you want to export holds state in addition to methods, you should stick to a simple export default myObject. Otherwise, calling the imported methods won't work as intended, since the context of the object is lost.
As an example, the following object should be exported as a whole, since the properties of the object should stay encapsulated. Only importing and calling the increment function would not mutate myObject since the context of the object cannot be provided (since it's not imported as a whole).
const myObject = {
counter: 0,
increment: function() {
this.counter++;
}
}
export default myObject;
es6 native way to do this:
// file1.es6
export const myFunc = (param) => {
doStuff(param)
}
export const otherFunc = ({ param = {} }) => {
doSomething({ ...param })
}
// file2.es6
import { otherFunc } from './file1.es6'
import * as MyLib from './file1.es6'
MyLib.myfunc(0)
MyLib.otherFunc({ who: 'Repley' })
otherFunc({ var1: { a1: 1 } })
And so on.

Importing an instantiated class sets global prototype

Given the following react component, I am able to import the following component from multiple components. I'm confused on why after importing into each module and calling increment, it will increment the value as if it were the same instance. I don't think it's attached to the 'window', because I did some inspection.
Could this be, because this sets a global prototype? That still doesn't explain why it's seemingly updating the same instantiated class.
import React, { Component } from 'react';
class FeatureFlags extends Component {
constructor (props) {
super (props);
this.featureFlagList = [ 'test': true ];
this.i = 0;
}
get showFeatureFlagList () {
return this.featureFlagList;
}
increment () {
this.i++;
return this.i;
}
setList (list) {
this.featureFlagList = list;
return this.featureFlagList;
}
render () {
return (
<div></div>
);
}
}
export default new FeatureFlags;
//First component -
import FeatureFlags from './FeatureFlags';
console.log('first module ', FeatureFlags.increment() ); //Logs 1
//Second component
import FeatureFlags from './FeatureFlags';
console.log('second module ', FeatureFlags.increment() ); //Logs 2
No, it's because you
export default new FeatureFlags;
That's one single instance! Importing the module multiple times will always import the same value.
Instead, you should always export the class:
export default class FeatureFlags extends Component { … }
and instantiate it in the other modules as often as you need it:
import FeatureFlags from './FeatureFlags';
const myLocalFlags = new FeatureFlags;
console.log('first module ', myLocalFlags.increment()); //Logs 1

How to test decorated React component with shallow rendering

I am following this tutorial: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/
Trying to learn how "shallow rendering" works.
I have a higher order component:
import React from 'react';
function withMUI(ComposedComponent) {
return class withMUI {
render() {
return <ComposedComponent {...this.props}/>;
}
};
}
and a component:
#withMUI
class PlayerProfile extends React.Component {
render() {
const { name, avatar } = this.props;
return (
<div className="player-profile">
<div className='profile-name'>{name}</div>
<div>
<Avatar src={avatar}/>
</div>
</div>
);
}
}
and a test:
describe('PlayerProfile component - testing with shallow rendering', () => {
beforeEach(function() {
let {TestUtils} = React.addons;
this.TestUtils = TestUtils;
this.renderer = TestUtils.createRenderer();
this.renderer.render(<PlayerProfile name='user'
avatar='avatar'/>);
});
it('renders an Avatar', function() {
let result = this.renderer.getRenderOutput();
console.log(result);
expect(result.type).to.equal(PlayerProfile);
});
});
The result variable holds this.renderer.getRenderOutput()
In the tutorial the result.type is tested like:
expect(result.type).toEqual('div');
in my case, if I log the result it is:
LOG: Object{type: function PlayerProfile() {..}, .. }
so I changed my test like:
expect(result.type).toEqual(PlayerProfile)
now it gives me this error:
Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]
So PlayerProfile's type is the higher order function withMUI.
PlayerProfile decorated with withMUI, using shallow rendering, only the PlayerProfile component is rendered and not it's children. So shallow rendering wouldn't work with decorated components I assume.
My question is:
Why in the tutorial result.type is expected to be a div, but in my case isn't.
How can I test a React component decorated with higher order component using shallow rendering?
You can't. First let's slightly desugar the decorator:
let PlayerProfile = withMUI(
class PlayerProfile extends React.Component {
// ...
}
);
withMUI returns a different class, so the PlayerProfile class only exists in withMUI's closure.
This is here's a simplified version:
var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});
You pass the value to the function, it doesn't give it back, you don't have the value.
The solution? Hold a reference to it.
// no decorator here
class PlayerProfile extends React.Component {
// ...
}
Then we can export both the wrapped and unwrapped versions of the component:
// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;
The normal code using this component doesn't change, but your tests will use this:
import {undecorated as PlayerProfile} from '../src/PlayerProfile';
The alternative is to mock the withMUI function to be (x) => x (the identity function). This may cause weird side effects and needs to be done from the testing side, so your tests and source could fall out of sync as decorators are added.
Not using decorators seems like the safe option here.
Use Enzyme to test higher order / decorators with Shallow
with a method called dive()
Follow this link, to see how dive works
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md
So you can shallow the component with higher order and then dive inside.
In the above example :
const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)
Similarly you can access the properties and test it.
You can use 'babel-plugin-remove-decorators' plugin. This solution will let you write your components normally without exporting decorated and un-decorated components.
Install the plugin first, then create a file with the following content, let us call it 'babelTestingHook.js'
require('babel/register')({
'stage': 2,
'optional': [
'es7.classProperties',
'es7.decorators',
// or Whatever configs you have
.....
],
'plugins': ['babel-plugin-remove-decorators:before']
});
and running your tests like below will ignore the decorators and you will be able to test the components normally
mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive
I think the above example is confusing because the decorator concept is used interchangeably with idea of a "higher order component". I generally use them in combination which will make testing/rewire/mocking easier.
I would use decorator to:
Provide props to a child component, generally to bind/listen to a flux store
Where as I would use a higher order component
to bind context in a more declarative way
The problem with rewiring is I don't think you can rewire anything that is applied outside of the exported function/class, which is the case for a decorator.
If you wanted to use a combo of decorators and higher order components you could do something like the following:
//withMui-decorator.jsx
function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///bind here based on some getter
};
}
render() {
return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
}
};
}
//higher-order.jsx
export default function(ChildComp) {
#withMui //provide store bindings
return class HOC extends Component {
static childContextTypes = {
getAvatar: PropTypes.func
};
getChildContext() {
let {store1} = this.props;
return {
getAvatar: (id) => ({ avatar: store1[id] });
};
}
}
}
//child.js
export default Child extends Component {
static contextTypes = {
getAvatar: PropTypes.func.isRequired
};
handleClick(id, e) {
let {getAvatar} = this.context;
getAvatar(`user_${id}`);
}
render() {
let buttons = [1,2,3].map((id) => {
return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
});
return <div>{buttons}</div>;
}
}
//index.jsx
import HOC from './higher-order';
import Child from './child';
let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);
Then when you want to test you can easily "rewire" your stores supplied from the decorator because the decorator is inside of the exported higher order component;
//spec.js
import HOC from 'higher-order-component';
import Child from 'child';
describe('rewire the state', () => {
let mockedMuiDecorator = function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///mock that state here to be passed as props
};
}
render() {
//....
}
}
}
HOC.__Rewire__('withMui', mockedMuiDecorator);
let MyComponent = HOC(Child);
let child = TestUtils.renderIntoDocument(
<MyComponent {...mockedProps} />
);
let childElem = React.findDOMNode(child);
let buttons = childElem.querySelectorAll('button');
it('Should render 3 buttons', () => {
expect(buttons.length).to.equal(3);
});
});
I'm pretty sure this doesn't really answer your original question but I think you are having problems reconciling when to use decorators vs.higher order components.
some good resources are here:
http://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/
https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
https://github.com/badsyntax/react-seed/blob/master/app/components/Menu/tests/Menu-test.jsx
https://github.com/Yomguithereal/baobab-react/blob/master/test/suites/higher-order.jsx
In my case decorators are very useful and I dont want to get rid of them (or return wrapped and unwrapped versions) im my application.
The best way to do this in my opinion is to use the babel-plugin-remove-decorators (which can be used to remove them in tests) has Qusai says, but I wrote the pre-processor differently like below:
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {
filename: filename,
plugins: ['babel-plugin-remove-decorators:before']
}).code;
}
return src;
}
};
Take notice of the babel.transform call that im passing the babel-plugin-remove-decorators:before element as an array value, see: https://babeljs.io/docs/usage/options/
To hook this up with Jest (which is what I used), you can do it with settings like below in your package.json:
"jest": {
"rootDir": "./src",
"scriptPreprocessor": "../preprocessor.js",
"unmockedModulePathPatterns": [
"fbjs",
"react"
]
},
Where preprocessor.js is the name of the preprocessor.

Categories

Resources