Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uncaught TypeError: *** is not a function in a React component

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());

like image 410
Greg Forel Avatar asked Mar 12 '17 19:03

Greg Forel


1 Answers

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:

  • define a default value for the function.
  • execute the function in 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.
like image 64
ballenf Avatar answered Oct 27 '22 11:10

ballenf