I'm trying to set up an app using ES6 classes for the first time, but having difficulty calling a function from an imported class.
The Class to be imported:
import {BaseElement} from './ui/base-element.js';
export class Product extends BaseElement {
constructor() {
super()
this.toggleAttributes();
}
// show more attributes toggle
toggleAttiributes () {
const toggleButton = document.querySelectorAll('.smrt42-toggle-attr')
document.addEventListener('click', (e)=>{
const t = e.target;
if(t.className.indexOf('smrt42-toggle-attr') !== -1) {
e.preventDefault()
let productAttrs = t.parentNode.previousElementSibling
if(t.classList.contains('smrt42-toggle-attr-more')) {
productAttrs.classList.add('smrt42-attr-open')
} else if (t.classList.contains('smrt42-toggle-attr-less')) {
productAttrs.classList.remove('smrt42-attr-open')
}
}
})
}
}
Importing the Class here:
import {BaseElement} from './ui/base-element.js';
import {Product} from './product.js';
export class Content extends BaseElement {
constructor() {
super()
let p = new Product() ;
}
}
This gives a console error this.toggleAttributes is not a function
You have mistyped the name of the function. In declaration you have the name toggleAttiributes, but you call it as toggleAttributes. Extra i is here
toggleAttiributes
---------^-------
Related
I write UI tests using PageObject Pattern (NodeJS + Webdriverio) and I have a base class (BasePage), a page class(MyPage), a popup component class(PopupComponentClass) with multipurpose behaviour, a popup class with a specific implementation(SpecificPopupComponentClass). I need to extend SpecificPopupComponentClass from PopupComponentClass.
page.js:
export default class BasePage {
get BASE_URL() {
return "https://url.com";
};
...some methods...
}
my.page.js:
import BasePage from "../page";
class MyPage extends BasePage {
constructor() {
super();
};
get URL() { return `${this.BASE_URL}/some/path` };
get title() { return $("h1") };
orderRandomTariff() {
...some actions...
};
}
export default new MyPage ();
popup.component.page.js:
import BasePage from "../pages/page";
class PopupComponent extends BasePage{
constructor() {
super();
};
get title() { return $("h1") };
}
export default new PopupComponent();
specific.popup.component.js:
import PopupComponent from "./popupFragment";
class SpecificPopupComponent extends PopupComponent {
constructor() {
super();
};
get popupStreetInput() { return $(".//div[#class='checkAddress']//*[./*[contains(text(),'Street')]]//input") };
fillStreet(street) {
...some actions with this.popupStreetInput...
};
}
export default new SpecificPopupComponent();
...and trying to use it in test.js:
import MyPage from "../../../../pages/my.page";
import SpecificPopupComponent from "../../../../fragments/specific.popup.component";
const myPage= MyPage ;
const popup = SpecificPopupComponent ;
describe("Test", () => {
before(() => {
myPage.open();
});
it("Check", () => {
popup.fillStreet("Street");
});
});
but I'm getting an error: "TypeError: Class extends vlaue #PopupComponent is not a constructor or null".
I suspect this is due to circular dependencies, but I can't understand, what I need to do to fix that.
It seems the issue here is how you're exporting the base class. You're instantiating it instead of just exporting the class you'll inherit from. It should be like this:
export default class PopupComponent extends BasePage {
constructor() {
super();
};
get title() { return $("h1") };
}
And then create the instance when you're going to use it
var myPage = new MyPage()
var popup = new SpecificPopupComponent()
myPage.open()
popup.fillStreet('street')
What you're doing there is equivalent to doing this:
class SpecificPopupComponent extends new PopupComponent() {
// ... what am I? 🤔
}
I suspect this is due to circular dependencies
You can validate or reject your suspects by copying all the code into the test file in the correct order without using imports. But I don't think it's an circular dependency
I created this Plunker to remove the usage of imports and prove this.
I am trying to run the code below, but it is not working. I think this is a scope problem, but I'm not sure how to fix this.
import CommonController from './CommonController';
import CategoryService from './category/Service.js';
class CategoryController extends CommonController {
constructor() {
super(CategoryService);
}
}
export default new CategoryController();
// ===================CommonController==========================
export default class CommonController {
constructor(service) {
this.service = service;
}
async get () {
console.log(this); // it returns undefined
}
}
// ===================CategoryService==========================
import Category from './Category'
import dto from './dto'
class CategoryService extends CommonService {
constructor() {
super(Category, dto);
}
}
export default new CategoryService();
// ===================CommonService==========================
export default class CommonService {
constructor(model, dto) {
this.model = model;
this.dto = dto;
}
}
if a run:
import CategoryController from './CategoryController';
CategoryController.get()
the console.log in CommonController get function will print undefined
Am I doing something wrong?
The issue is that you are calling get() on the class itself, instead of calling it on an instance of the class. Try creating an instance of CategoryController, like so:
cc = new CategoryController();
Then, you should be able to call:
cc.get();
Demo in the code below (same as yours, just slightly modified to reflect my point).
// ===================CommonController==========================
class CommonController {
constructor(service) {
this.service = service;
}
async get () {
console.log(this); // it returns undefined
}
}
// ===================CommonService==========================
class CommonService {
constructor(model, dto) {
this.model = model;
this.dto = dto;
}
}
// ===================CategoryService==========================
class CategoryService extends CommonService {
constructor() {
super(Category, dto);
}
}
class CategoryController extends CommonController {
constructor() {
super(CategoryService);
}
}
cs = new CategoryController();
cs.get();
Say I have a generic class module:
export class MyCalc {
data = {}
...
}
And say I want to extend more functionality:
export class MyCalcLoader {
load = some_data => {
this.data = some_data;
}
}
export class MyCalcUI {
print = () => {
document.write(JSON.stringify(this.data));
}
}
What is the appropriate way to extend MyCalc and also use those extensions/plugins?
import {MyCalc} from "./MyCalc.js";
import {MyCalcLoader} from "./MyCalcLoader.js";
import {MyCalcUI} from "./MyCalcUI.js";
// TODO: MakeMyCalcExtendLoaderAndUi();
class BankingCalc extends MyCalc {
config = {...}
constructor() {
super();
}
}
const banking_calc = new BankingCalc();
banking_calc.load({...});
banking_calc.print();
I've thought through a few different janky ways to do this, but I'm sure this is common enough and that there's a right way to do it with vanilla ES6.
You could use Mixins:
export const MyCalcLoader = Super => class MyCalcLoader extends Super {
load = some_data => {
this.data = some_data;
}
}
export const MyCalcUI = Super => class MyCalcUI extends Super {
print = () => {
document.write(JSON.stringify(this.data));
}
}
Then compose the class as:
class BankingCalc extends MyCalcLoader(MyCalcUI(MyCalc)) {
//...
}
In the following, JSON data is fetching successfully from a parent component but the function breakLine() doesn't work when called inside the component, throwing the following error message.
'breakLine' is not defined no-undef
json.db
{
"studies": [
{
"text": [
"first line here-=br=-second line here"
]
}
}
component code:
import React, { Component } from 'react'
class PortfolioPage extends Component {
constructor(props) {
super(props);
this.state = {
resultText: []
}
}
componentDidMount() {
this.setState({
resultText: this.props.data.text
})
}
breakLine(text){
text.replace("-=br=-", "\n");
}
render() {
const { resultText } = this.state;
return (
<div className="portfolio-pages">
<p>
{breakLine(resultText)}
</p>
</div>
)
}
export default PortfolioPage
you need to bind this function to the context. try to use
class PortfolioPage extends Component {
constructor(props) {
...
this.breakLine = this.breakLine.bind(this)
}
or arrow style
breakLine = (text) => {...}
Bind the function to current context and use this keyword:
breakLine = (text) => text.join(' ').replace("-=br=-", "\n");
// [...code...]
{this.breakLine(resultText)}
The this. is missing, you should call it with this.breakLine(resultText).
Though your resultText is of type string[], so you will want to define the breakLine function as follows:
text.map(t => t.replace("-=br=-", "\n"))
if you want to have one line per array item, you could also do the following:
text.join("\n").replace("-=br=-", "\n").split("\n")
I'm learning React and I'm not sure how to setup this pattern. It could be something really easy I'm just missing.
I have a main component that controls state. It has all of the functions to update state and passes these down to child components via props. I've simplified the code to focus on one of these functions.
Here's the component now, all works as it should:
ManageMenu.js
import React from 'react'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = this.toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
render() {
return (
...
)
}
}
export default ManageMenu
The toggleEditing is passed via props to a child component that uses it to render an editing form if the edit button is clicked.
I have about 10 of these different functions in this component and what I would like to do is move them to an external lib/methods.js file and then reference them. Below is the code I would like to have, or something similar, but React doesn't like what I'm doing. Throws a syntax error:
Failed to compile.
Error in ./src/components/ManageMenu.js
Syntax error: Unexpected token
toggleEditing(id, menuSectionId, this.state, this)
Here is what I would like to do...
lib/methods.js
const toggleEditing = function(id, state, that) {
const menuSections = state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
that.setState({ menuSections })
}
module.exports = {
toggleEditing
}
And then in my component:
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
// Set initial state
this.state = {
menuSections: []
}
}
toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu
Any help is appreciated, thanks!
Thanks to #Nocebo, the answer on how to externalize functions is here:
Externalise common functions in various react components
In my particular situation,
I need to remove the “floating” toggleEditing(id, this.state, this) call in the middle of nowhere. Update: This error happens “because it is invoking a method within a class definition.” (see Pineda’s comment below)
Remove the leading this. on the right side of the this.toggleEditing statement in constructor()
Update the function in lib/methods.js to remove the state and that variables since its bound to this in the constructor()
See updated code below.
ManageMenu.js
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.toggleEditing = toggleEditing.bind(this)
// Set initial state
this.state = {
menuSections: []
}
}
render() {
return (
...
)
}
}
export default ManageMenu
lib/methods.js
const toggleEditing = function(id) {
const menuSections = this.state.menuSections
menuSections.map(key => (key.id === id ? key.details.editing = id : ''))
this.setState({ menuSections })
}
module.exports = {
toggleEditing
}
You're error arises because you are invoking toggleEditing in your ManageMenu.js class definition rather than defining a function.
You can achive what you want by setting a local class member this.toggleEditing to the bound function returned by the .bind method and do so within the constructor:
import React from 'react'
import { toggleEditing } from '../lib/methods'
class ManageMenu extends React.Component {
constructor() {
super()
this.state = {
menuSections: []
}
// bind external function to local instance here here
this.toggleEditing = toggleEditing.bind(this);
}
// don't invoke it here, bind it in constructor
//toggleEditing(id, this.state, this)
render() {
return (
...
)
}
}
export default ManageMenu