I'm passing an object as an argument in a React component's constructor. Then, I want to call one of the object's function, but get this error message:
Uncaught TypeError: _this.layout.getWbsLayout is not a function
at new Wbs (Wbs.tsx:50)
at react-dom.js:4749
at measureLifeCyclePerf (react-dom.js:4529)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner (react-dom.js:4748)
at ReactCompositeComponentWrapper._constructComponent (react-dom.js:4734)
at ReactCompositeComponentWrapper.mountComponent (react-dom.js:4642)
at Object.mountComponent (react-dom.js:11542)
at ReactCompositeComponentWrapper.performInitialMount (react-dom.js:4825)
at ReactCompositeComponentWrapper.mountComponent (react-dom.js:4712)
at Object.mountComponent (react-dom.js:11542)
I read some posts about binding methods to this in the constructor, but as I am calling a function of an object I passed as an argument, I am not sure this applies here, and still got a similar message saying bind is not a function
.
Here's my code, Wbs
is the root React component, WbsLayout
is an object I pass as a parameter to Wbs' constructor.
The error happens in the constructor, when I do this.layout.getWbsLayout()
Note that I tried to call the function in ComponentDidMount
instead, but got the same error.
import * as React from 'react';
import WbsLayout from "../layout/WbsLayout";
interface WbsProps {}
interface WbsState {
wbsElements: any[];
wbsEdges: any[]
}
class Wbs extends React.Component<WbsProps, WbsState>{
public cy: Cy.Instance;
private layout: WbsLayout;
private s: snap.Paper;
private renderer: WbsRenderer;
public state : WbsState;
constructor(props: WbsProps, layout: WbsLayout) {
super(props);
this.layout = layout;
this.cy = this.layout.cy;
// this does show a json object in the console, hence layout can be accessed
console.log(layout.cy.elements().jsons());
this.layout.getWbsLayout(); //The error happens here
// initial state
this.state = {
wbsElements: [],
wbsEdges: [],
};
}
public render() {
const wbsElements: any = this.state.wbsElements.map((element: any) => (
<WbsElement
key={element.wbsElementBox.nodeId}
wbsElementBox={...element.wbsElementBox}
wbsElementTitle={...element.wbsElementTitle}
wbsElementId={...element.wbsElementId}
/>
));
const wbsEdges: any = this.state.wbsEdges.map((edge: any) => (
<WbsEdge
key={edge.edgeId}
edgeId={edge.edgeId}
sourceWbsElementData={...edge.sourceWbsElementData}
targetWbsElementData={...edge.targetWbsElementData}
/>
));
return (
<svg>
{wbsElements}
{wbsEdges}
</svg>
);
}
}
export default Wbs;
Here's WbsLayout
:
export class WbsLayout {
public cy: Cy.Instance;
constructor(graph: any, Options?: Options) {
this.cy = cytoscape({
headless: true,
elements: graph
});
}
public getWbsLayout(): any {
const wbsElements: any = this.cy.collection("node[visibility = 'visible']").map((n: Cy.CollectionNodes) =>
({
wbsElementBox: {
nodeId: n.data().id,
x: n.data().x,
y: n.data().y,
width: n.data().width,
height: n.data().height
},
wbsElementTitle: {
text: n.data().title
},
wbsElementId: {
text: n.data().wbsId
}
})
);
const wbsEdges: any = [
{
edgeId: '5',
sourceWbsElementData: {
nodeId: '1',
x:464.359375,
y:30,
width:100,
height:40,
layoutStyle: 0
},
targetWbsElementData: {
nodeId:'1.5',
x:867.875,
y:100,
width:130.84375,
height:40,
layoutStyle: 1
}
}
];
return {
wbsElements,
wbsEdges
};
}
....
}
export default WbsLayout;
Here's how I instantiate Wbs:
let graph: any = {
nodes: [],
edges: []
};
let layout: WbsLayout = new WbsLayout(graph);
let wbs = new Wbs(null, layout);
I can see json objects in Wbs' constructor: console.log(layout.cy.elements().jsons());
EDIT: Problem turned out to be unrelated, but it's worth remembering that React is very good at throwing early warnings about these kinds of things in that it will 'test' your components and throw errors or warnings if things don't 'look' right, e.g., if props aren't coming in as expected. The early warnings are sometimes so 'helpful' that they can throw off your normal debugging instincts.
Original answer:
Either shield the function call from executing before the props get passed in or set default props setting some kind of dummy or fallback function behavior.
In the constructor:
if (this.layout.getWbsLayout) {
this.layout.getWbsLayout();
}
Other options:
componentWillMount
or componentDidMount
, depending on your needs. Lifecycle hooks are the right place to execute data instantiation. The constructor is best for state and static calls like binding your class's functions, as needed.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With