Question

There are several points on a canvas, clicking one should show a toolbar that allows to edit x and y coordinates of the selected point.

JSFiddle

I tried:

/**
 * @jsx React.DOM
 */
var EditorCanvas = React.createClass({
    getInitialState: function() {
        return {
            points: [
                {
                    x: 50,
                    y: 100
                },
                {
                    x: 200,
                    y: 50
                }
            ],
            selected: null
        };
    },

    render: function() {
        return <div>
            <Toolbar point={this.state.selected}/>
            <div id="canvas">
                {this.state.points.map(this.renderPoint)}
            </div>
        </div>;
    },

    renderPoint: function(point) {
        return <Point x={point.x} y={point.y} onClick={this.selectPoint.bind(this, point)}/>
    },

    selectPoint: function(point) {
        this.setState({selected: point});
    },

    renderToolbar: function() {
        var selected = this.state.selected;
        if (!selected) {
            return <div/>
        }
        return <div>
            x:<input type="number" value={selected.x} onInput={this.xChange}/>
            &middot;
            y:<input type="number" value={selected.y} onInput={this.yChange}/>
        </div>
    }
});

var Toolbar = React.createClass({
    render: function() {
        var point = this.props.point;
        if (!point) {
            return <div id="toolbar"/>;
        }
        return <div id="toolbar">
            x:<input type="number" value={point.x} onInput={this.xChange}/>
            &middot;
            y:<input type="number" value={point.y} onInput={this.yChange}/>
        </div>;
    },

    xChange: function(e) {
        this.setState({selected: {x: e.target.value}});
    },
    yChange: function(e) {
        this.setState({selected: {y: e.target.value}});
    }
});


var Point = React.createClass({
    render: function() {
        var style = {
            left: this.props.x + 'px',
             top: this.props.y + 'px'
        };
        return <div style={style} className="point" onClick={this.props.onClick}/>
    }
});

React.renderComponent(
    <EditorCanvas/>,
    document.body
);

It didn’t work because state.selected is a copy of a point object, not a pointer to it.

How can I make it work?

Was it helpful?

Solution

Your EditorCanvas expects its points array in its state to be the source of truth, but the changes in Toolbar are not making it back to that array. The Toolbar creates its own state, which is not persisted to the EditorCanvas. I suggest keeping all state in EditorCanvas and passing the changes from the Toolbar to the canvas with a callback.

Working JSFiddle: http://jsfiddle.net/krza5/2/

var EditorCanvas = React.createClass({
    getInitialState: function() {
        return {
            points: [
                {
                    x: 50,
                    y: 100
                },
                {
                    x: 200,
                    y: 50
                }
            ],
            selected: null
        };
    },

    render: function() {
        return <div>
            <Toolbar point={this.state.selected}
                onXChange={this.setSelectedX}
                onYChange={this.setSelectedY}/>
            <div id="canvas">
                {this.state.points.map(this.renderPoint)}
            </div>
        </div>;
    },

    renderPoint: function(point) {
        return <Point point={point} onClick={this.selectPoint.bind(this, point)}/>
    },

    selectPoint: function(point) {
        this.setState({selected: point});
    },

    setSelectedX: function(event) {
        var point = this.state.selected;
        point.x = event.target.value;
        this.forceUpdate();
    },

    setSelectedY: function(event) {
        var point = this.state.selected;
        point.y = event.target.value;
        this.forceUpdate();
    }
});

var Toolbar = React.createClass({
    render: function() {
        var point = this.props.point;
        if (!point) {
            return <div id="toolbar"/>;
        }
        return <div id="toolbar">
            x:<input type="number" value={point.x} onInput={this.props.onXChange}/>
            &middot;
            y:<input type="number" value={point.y} onInput={this.props.onYChange}/>
        </div>;
    }
});

var Point = React.createClass({
    render: function() {
        var style = {
            left: this.props.point.x + 'px',
             top: this.props.point.y + 'px'
        };
        return <div style={style} className="point" onClick={this.props.onClick}/>
    }
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top