简体   繁体   中英

Dynamically generating components in React-Bootstrap

I'm trying to dynamically generate alert components in React-Bootstrap at runtime by instantiating the components from Javascript classes. I'm doing this because there are lots of alerts to show and because Javascript classes are more succinct to create.

My attempts to do this are not working. I'm not sure whether the problem generally applies to React or just to React-Bootstrap. However, the error occurs in react.js, which throws the following:

TypeError: undefined is not a function

The throw occurs in the alert.getComponent() call in the following JSX file:

/** @jsx React.DOM */

var Alert = ReactBootstrap.Alert;

var AlertDismissible = React.createClass({
    getInitialState: function() {
        return {
            isVisible: true
        };
    },

    render: function() {
        if(!this.state.isVisible)
            return null;

        var message = this.props.message;
        if(this.props.code !== null)
            message = message +'(Code '+ this.props.code +')';
        return (
            <Alert bsStyle={this.props.level} onDismiss={this.dismissAlert}>
                <p>{message}</p>
            </Alert>
        );
    },

    dismissAlert: function() {
        this.setState({isVisible: false});
    }
});

function AlertNotice(level, message, code) {
    this.level = level;
    this.message = message;
    this.code = code || null;
}

AlertNotice.prototype.getComponent = function() {
    // What should go here? Using React.createClass() doesn't help
    return (
        <AlertDismissible level={this.level} message={this.message}
                code={this.code} />
    );
};

function SuccessAlert(message) {
    AlertNotice.call(this, 'success', message);
}
SuccessAlert.prototype = Object.create(AlertNotice);
SuccessAlert.prototype.constructor = SuccessAlert;

/* ...more kinds of alerts... */

function ErrorAlert(message, code) {
    AlertNotice.call(this, 'danger', message, code);
}
ErrorAlert.prototype = Object.create(AlertNotice);
ErrorAlert.prototype.constructor = ErrorAlert;

var SomethingWithAlerts = React.createClass({
    render: function() {
        var alerts = [
            new ErrorAlert("Goof #1", 123),
            new ErrorAlert("Goof #2", 321)
        ].map(function(alert) {
            // react.js throws "TypeError: undefined is not a function"
            return alert.getComponent();
        });
        return (
            <div>{alerts}</div>
        );
    }
});

var TestComponent = (
    <div>
        <SomethingWithAlerts />
    </div>
);

React.renderComponent(
  TestComponent,
  document.getElementById('content')
);

The Alert component comes from the React-Bootstrap library. The div components seem extraneous but I found them necessary to satisfy the react framework. In reality, I'll be storing the AlertNotice instances in react state and then generating react nodes from them.

What is the proper way to go about this?

Here's a hint. If I replace return alert.getComponent(); with the following hardcoded alert, the AlertDismissible components render without error (in duplicate) but I get a warning:

return (
    <AlertDismissible level="danger" message="Goof" code="777" />
);

The following is the warning message I get with the above replacement, including a link that explains I should set key= to a unique for each alert:

Each child in an array should have a unique "key" prop. Check the render method
of SpecimenSetManager. See http://fb.me/react-warning-keys for more information.

However, if I simply replace the code inside of AlertNotice.prototype.getComponent with the above hardcoded alert, I get the same TypeError message as before.

For completeness, here is my HTML source. This is react and react-boostrap v0.11.1

<html>
  <head>
    <script src="lib/react.js"></script>
    <script src="lib/react-bootstrap.js"></script>
    <script src="lib/JSXTransformer.js"></script>
    <link rel="stylesheet" href="css/bootstrap-theme.min.css">
    <link rel="stylesheet" href="css/bootstrap.min.css">
  </head>
  <body>
    <div id="content"></div>
    <script src="components.js" type="text/jsx"></script>
  </body>
</html>

I solved the problem. The solution was to create a special react component that represents a set of alerts. Apparently it is only possible to reference automatic or object variables in component parameters from within the React.createClass() definition. Perhaps this is a syntactic constraint of JSX rather than a logical constraint of react.

I don't understand why this solution works. I would like to understand so that I don't have to deal with similar problems again in the future. If you can explain the general principle I'm violating and the general principle that should be followed instead -- something more insightful than what I've stated here -- then I'll mark your response as the "answer" to this question. I'd like to know how much flexibility I really have.

Here's the code that works, including a new AlertSet component:

/** @jsx React.DOM */

function AlertNotice(level, message, code) {
    this.level = level;
    this.message = message;
    this.code = code || null;
}

function SuccessAlert(message) {
    AlertNotice.call(this, 'success', message);
}
SuccessAlert.prototype = Object.create(AlertNotice);
SuccessAlert.prototype.constructor = SuccessAlert;

/* ...more kinds of alerts... */

function ErrorAlert(message, code) {
    AlertNotice.call(this, 'danger', message, code);
}
ErrorAlert.prototype = Object.create(AlertNotice);
ErrorAlert.prototype.constructor = ErrorAlert;

var Alert = ReactBootstrap.Alert;

var AlertDismissible = React.createClass({
    getInitialState: function() {
        return {
            isVisible: true
        };
    },

    render: function() {
        if(!this.state.isVisible)
            return null;

        var message = this.props.message;
        if(this.props.code !== null)
            message = message +'(Code '+ this.props.code +')';
        return (
            <Alert bsStyle={this.props.level} onDismiss={this.dismissAlert}>
                <p>{message}</p>
            </Alert>
        );
    },

    dismissAlert: function() {
        this.setState({isVisible: false});
    }
});

var AlertSet = React.createClass({
    render: function() {
        var alerts = this.props.alerts.map(function(alert, i) {
            return (
                <AlertDismissible key={"alert-"+i} level={alert.level}
                        message={alert.message} code={alert.code} />
            );
        });
        // component must be a single node, so wrap in a div
        return (
            <div>{alerts}</div>
        );
    }
});

var SomethingWithAlerts = React.createClass({
    render: function() {
        var alerts = [
            new ErrorAlert("Goof #1", 123),
            new ErrorAlert("Goof #2", 321)
        ];
        return (
            <AlertSet alerts={alerts} />
        );
    }
});

// TestComponent returns a single node, so doesn't need a div
var TestComponent = (
    <SomethingWithAlerts />
);

React.renderComponent(
  TestComponent,
  document.getElementById('content')
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM