ExtJS with React, scope confusion - javascript

In my app I use ExtJS with React. I've tried to override some functionalities but I faced some problems when defining custom Ext components. I suspect that this is caused by different scopes of "this".
There are 2 scenarios. The 1st one is working scenario, but achieved in non-elegant way. The 2nd scenario is desired but it doesn't work.
Scenario 1 - it works - codepen #1
index.js
// defined globally, it's not very nice
Ext.define('CustomContextMenu', {
extend: 'Dummy.plugin.ContextMenu',
createMenuItems: function() {
return this.callParent() // keep standard behaviour
}
});
ReactDOM.render(
React.createElement(CustomScheduleApp),
document.getElementById('schedule-app')
);
app.jsx
class CustomScheduleApp extends React.Component {
...
render() {
<CustomSchedule ...></CustomSchedule>
}
}
schedule.jsx
class CustomSchedule extends React.Component {
...
componentDidMount() {
let taskContextMenu = Ext.create("CustomContextMenu");
...
}
}
Scenario 2 - it doesn't work - codepen #2
index.js
ReactDOM.render(
React.createElement(CustomScheduleApp),
document.getElementById('schedule-app')
);
app.jsx
class CustomScheduleApp extends React.Component {
...
render() {
<CustomSchedule ...></CustomSchedule>
}
}
schedule.jsx
class CustomSchedule extends React.Component {
...
componentDidMount() {
Ext.define('CustomContextMenu', {
extend: 'Dummy.plugin.ContextMenu',
createMenuItems: function() {
return this.callParent() // keep standard behaviour
// "this" has fewer keys in this scenario, some data is missing
}
});
let taskContextMenu = Ext.create("CustomContextMenu");
...
}
}
Error message for Scenario #2:
Uncaught TypeError: Cannot read property 'apply' of null
I have a basic knowledge about scopes, I'm not even sure if it's more React of JavaScript issue. Am I missing something in my code?

Because the way React works and you are setting your extended method inside a React prototype, ExtJS callParent is getting confused about where the event is called from, so you would have to specify this manually.
addBodyCls: function(cls) {
// custom logic here
console.log("inside addBodyCls");
return this.superclass.addBodyCls.apply(this, arguments);
}
should do the trick!

Related

npm debounce failing in ReactJS

Using npm debounce I get an error with the following code in ReactJS. The error
Javascript - Uncaught TypeError: Object(...) is not a function
happens when the function is passed into debounce()
import React, { Component } from 'react';
import { debounce } from 'debounce';
class test extends Component {
constructor(props) {
super(props);
this.state = {
yolo: {}
};
}
foobar(param) {
debounce(() => {
this.setState({yolo: param});
}, 99);
}
render () {
return (
...
<SomeComponent action={this.foobar.bind(this)} />
...
);
}
}
I have tried some of the solutions mentioned in Perform debounce in React.js
but none seem to work.
import React, { Component } from 'react';
import debounce from 'debounce';
class test extends Component {
constructor(props) {
super(props);
this.state = {
yolo: {}
};
this.foobar.bind(this);
}
foobar(param) {
debounce(() => {
this.setState({yolo: param});
}, 99);
}
render () {
return (
...
<SomeComponent action={this.foobar.bind(this)} />
...
);
}
}
The top set of code should work. Ok, so the reason why your call to foobar was not working before was because you were missing this line: this.foobar.bind(this);. Your previous syntax worked just fine and is actually preferable to the this.foobar =. Reason being because one is ES6 syntax and the other is ES5. What that bind function does when you call it is attach a particular this context for when the function is called. Here is a reference to an article that explains that: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Now the second part of this was the import. when you use the object bracket syntax, thats actually called object destructuring. So what that does is whatever that object exports it tries to access a debounce property and make that accessible in the current file. Problem is I suspect that that npm package is already export a function as its default so you don't need to access something on it. Make sense?
Hope this all helps! Best of luck (thumbsup)

React - How do you call a function from inside another function

Suppose I had this layout below:
class Navigation extends React.Component {
primaryFun() { console.log('funn') }
secondaryFun() {
this.primaryFun();
}
}
I'd of expected this to then call primary but instead I get an undefined, Ok.
So I thought I'd add a constructor to bind the function to this:
constructor(props) {
super(props)
this.primaryFun = this.primaryFun.bind(this);
}
but primary fun is still undefined.
In my real project I'm calling these on a mouseOut event.
Feels like the above should work and tbh the documentation for React is all over the shot so couldn't find much here.
Are you looking for something like this calling one function inside the other
import React, { Component } from 'react';
import './App.css'
class App extends Component {
constructor(){
super()
this.mouseClick = this.mouseClick.bind(this);
this.primaryFun = this.primaryFun.bind(this);
this.secondaryFun = this.secondaryFun.bind(this);
}
primaryFun(){
console.log('primaryFun funn')
}
secondaryFun(){
console.log('secondaryFun funn')
this.primaryFun()
}
mouseClick(){
this.secondaryFun()
}
render() {
return (
<div onClick={this.mouseClick}>
Hello World!
</div>
);
}
}
export default App;
Here when you click on "Hello world" secondaryFun is called and inside secondaryFun , primaryFun is been triggered
You also need to bind the secondaryFun function to use this inside that. Without that, the this inside the function secondaryFun will refers to the function scope which is secondaryFun
You need to bind this in your mouseOut
onMouseOut={this.secondaryFun.bind(this)}
Or a as best practice use the Lambda syntax. It'll bind this for you
onMouseOut={()=>this.secondaryFun()}
Make sure both functions have the correct this scope. If you are using class properties, see https://babeljs.io/docs/plugins/transform-class-properties/. Which already present on the babel-preset-react-app used by create-react-app, you can use that and write those as arrow functions, as seen on the babel link. And avoid having to use .bind on the constructor.
You must bind() both two functions.
You should that:
class Navigation extends React.Component {
constructor(props) {
super(props)
this.primaryFun = this.primaryFun.bind(this);
this.secondaryFun = this.secondaryFun.bind(this);
}
primaryFun() {
console.log('funn')
}
secondaryFun() {
this.primaryFun();
}
}

React Store is returning null for value of `this`

I'm running into an issue where this is returning null in my store for my React component. I believe I need to bind this, but I am unsure. I'm also using React Router and have my component set up in a wrapper so I'm able to pass props to it. Any help is appreciated!
COMPONENT
import React from 'react';
import PaymentStore from './../store/paymentStore';
class InitialPaymentScreen extends React.Component {
constructor(props) {
super(props)
}
render() {
console.log(this.props)
return (
<div className="payment-form-submit" onClick={this.props.store.init}>Next</div>
);
}
}
class PaymentForm extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<section className="main-content payment-form-wrapper">
<InitialPaymentScreen store={this.props.store}/>
</section>
);
}
}
export default class PaymentFormWrapper extends React.Component {
render() {
return (
<PaymentForm store={PaymentStore} mode="foo"/>
);
}
}
STORE
let PaymentStore = {
handleClickNext() {
console.log("hello")
},
init() {
console.log(this) // returns null
this.handleClickNext(); // cannot read property of null
},
};
export default PaymentStore;
Looks like you need to bind it indeed. You can do that by changing
<div className="payment-form-submit" onClick={this.props.store.init}>Next</div>
to
<div className="payment-form-submit" onClick={this.props.store.init.bind(this.props.store)}>Next</div>
# or
var init = this.props.store.init.bind(this.props.store);
<div className="payment-form-submit" onClick={init}>Next</div>
Alternatively, if InitialPaymentScreen only needs init from store, the binding could happen in PaymentForm, and InitialPaymentScreen could only receive the function.
The binding needs to be done because javascript is, well, unique in some ways.
Generally, a function's this is bound to literally what's it being called from. In your case, store.init() means the init function has this as store.
Now, if you assign store.init to a variable and call that, it doesn't know what's this! (this is probably the most common issue for people learning JS) — because during invocations, it tries to infer this by seeing which object that function is stored in.
To get make the function know what's its context then, you have two options basically:
a. fn.bind(something), which returns a bound function, or
b. create an anonymous function which invokes that one

Decorate react component to add lifecycle methods

I am trying to create a decorator method which will add some default lifecycle methods into the react component. My objective is to add some default functionality into the component, for example all component should be able to do a specific thing on componentWillMount.
I read a couple of articles and found this. It can be used to add new props to the react components.
export default function context(contextTypes, context) {
return function (DecoratedComponent) {
return class {
static childContextTypes = contextTypes;
getChildContext() {
return context;
}
render() {
return (
<DecoratedComponent {...this.props} />
);
}
}
}
}
But I am not sure how would I add class methods like componentWillMount. Can I do something like
Object.assign(DecoratedComponent.prototype, {
componentWillMount: () => {
// do something
}
})
Any idea towards right direction?
Refs:
http://asaf.github.io/blog/2015/06/23/extending-behavior-of-react-components-by-es6-decorators/
https://gist.github.com/motiz88/3db323f018975efce575
If you're using Babel with the stage 1 or stage 0 preset, you can use the following method:
First, define your decorator function, e.g.:
function lifecycleDefaults(target) {
target.prototype.componentWillMount = function() {
console.log('componentWillMount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
target.prototype.componentWillUnmount = function() {
console.log('componentWillUnmount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
target.prototype.componentDidMount = function() {
console.log('componentDidMount ran from decorator!');
console.log('this.props is still accessible', this.props);
}
}
Following that, decorate a component using the function you just defined e.g.:
#lifecycleDefaults
export class Page extends React.Component {
render() {
return (
<div>Hello decorators!</div>
);
}
};
Component 'Page' now has methods componentWillMount, componentDidMount, and componentWillUnmount. They run at the expected times in the component's lifecycle.
2 caveats: 1) I'm using the babel transform-decorators-legacy plugin; 2) I'm building my project using Webpack, with babel's transform-runtime included. YMMV.

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