Sortable: React on ES6 without mixins

Created on 5 Jan 2016  Â·  10Comments  Â·  Source: SortableJS/Sortable

Hello,

I wanted to integrate Sortable module with our code, but the problem is that we are using ES6 in our code.. As far as I know, mixins cannot be used in this situation, but they can be switched to Composed Components. Maybe there is anyone who already tried to convert that?

react help-wanted

Most helpful comment

A higher order React component (HOC) for Sortable is available at https://github.com/cheton/react-sortable.

You can run npm install --save react-sortablejs to install the package.

All 10 comments

Okay I've played around the mixin and created High Order Component from that and it works, but, wondering, maybe someone know how to get the internal instance of HOC in proper way, cause now I'm getting the instance of that component by accessing this._reactInternalInstance._renderedComponent._instance

Okay, figured it out. In HOC when I am passing ComposedComponent, in render method I am just adding ref attribute, and then when I want to get that instance I am just calling this.refs.[refName]. If someone would like to take a look at HOC version instead of mixin just reply here, I'll try to make a merge request then.

I would look to see your HOC implementation, currently looking for a Sortable solution for React and even tho Sortable isn't React specific I really like how it's coded.

I managed to get it working by rewriting existing react-sortable-mixin.js in ES6. See my example below:

react-sortable.jsx

import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import Sortable from 'sortablejs';

const defaultOptions = {
    ref: 'list',
    model: 'items',
    onStart: 'handleStart',
    onEnd: 'handleEnd',
    onAdd: 'handleAdd',
    onUpdate: 'handleUpdate',
    onRemove: 'handleRemove',
    onSort: 'handleSort',
    onFilter: 'handleFilter',
    onMove: 'handleMove'
};

let _nextSibling = null;
let _activeComponent = null;

const getModelName = (component) => {
    let { sortableOptions = {} } = component;
    let { model } = sortableOptions;
    return model || defaultOptions.model;
};

const getModelItems = (component) => {
    let model = getModelName(component);
    let { state = {}, props = {} } = component;
    let items = state[model] || props[model] || [];
    return items.slice();
};

class ReactSortable extends React.Component {
    _sortableInstance = null;

    componentDidMount() {
        const options = _.merge({}, defaultOptions, this.sortableOptions);
        const emitEvent = (type, evt) => {
            const method = this[options[type]];
            method && method.call(this, evt, this._sortableInstance);
        };
        let copyOptions = _.extend({}, options);

        [ // Bind callbacks so that 'this' refers to the component
            'onStart', 'onEnd', 'onAdd', 'onSort', 'onUpdate', 'onRemove', 'onFilter', 'onMove'
        ].forEach((name) => {
            copyOptions[name] = (evt) => {
                if (name === 'onStart') {
                    _nextSibling = evt.item.nextElementSibling;
                    _activeComponent = this;
                } else if (name === 'onAdd' || name === 'onUpdate') {
                    evt.from.insertBefore(evt.item, _nextSibling);

                    let newState = {};
                    let remoteState = {};
                    let oldIndex = evt.oldIndex;
                    let newIndex = evt.newIndex;
                    let items = getModelItems(this);

                    if (name === 'onAdd') {
                        let remoteItems = getModelItems(_activeComponent);
                        let item = remoteItems.splice(oldIndex, 1)[0];
                        items.splice(newIndex, 0, item);

                        remoteState[getModelName(_activeComponent)] = remoteItems;
                    } else {
                        items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]);
                    }

                    newState[getModelName(this)] = items;

                    if (copyOptions.stateHandler) {
                        this[copyOptions.stateHandler](newState);
                    } else {
                        this.setState(newState);
                    }

                    (this !== _activeComponent) && _activeComponent.setState(remoteState);
                }

                setTimeout(() => {
                    emitEvent(name, evt);
                }, 0);
            };

        });

        let domNode = ReactDOM.findDOMNode(this.refs[options.ref] || this);
        this._sortableInstance = Sortable.create(domNode, copyOptions);
    }
    componentWillReceiveProps(nextProps) {
        let newState = {};
        let model = getModelName(this);
        let items = nextProps[model];

        if (items) {
            newState[model] = items;
            this.setState(newState);
        }
    }
    componentWillUnmount() {
        this._sortableInstance.destroy();
        this._sortableInstance = null;
    }
}

export default ReactSortable;

Sample code:

import ReactSortable from './ReactSortable';

class SortableContainer1 extends ReactSortable {
    state = {
        items: [0, 1, 2, 3, 4]
    };
    sortableOptions = {
        group: 'shared'
    };

    render() {
        let items = this.state.items.map((text, index) => {
            return <li key={index}>{text}</li>;
        });

        return (
            <div>
                <ul ref="list">{items}</ul>
            </div>
        );
    }
}

class SortableContainer2 extends ReactSortable {
    state = {
        items: [5, 6, 7, 8, 9]
    };
    sortableOptions = {
        group: 'shared'
    };

    render() {
        let items = this.state.items.map((text, index) => {
            return <li key={index}>{text}</li>;
        });

        return (
            <div>
                <ul ref="list">{items}</ul>
            </div>
        );
    }
}

So you basically rewrote the entire script in ES6?

Sent from my iPhone

On Jan 27, 2016, at 2:24 AM, Cheton Wu [email protected] wrote:

I managed to get it working by rewriting existing react-sortable-mixin.js in ES6. See my example below:

react-sortable.jsx

import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import Sortable from 'sortablejs';

const defaultOptions = {
ref: 'list',
model: 'items',
onStart: 'handleStart',
onEnd: 'handleEnd',
onAdd: 'handleAdd',
onUpdate: 'handleUpdate',
onRemove: 'handleRemove',
onSort: 'handleSort',
onFilter: 'handleFilter',
onMove: 'handleMove'
};

let _nextSibling = null;
let _activeComponent = null;

const getModelName = (component) => {
let { sortableOptions = {} } = component;
let { model } = sortableOptions;
return model || defaultOptions.model;
};

const getModelItems = (component) => {
let model = getModelName(component);
let { state = {}, props = {} } = component;
let items = state[model] || props[model] || [];
return items.slice();
};

class ReactSortable extends React.Component {
_sortableInstance = null;

componentDidMount() {
    const options = _.merge({}, defaultOptions, this.sortableOptions);
    const emitEvent = (type, evt) => {
        const method = this[options[type]];
        method && method.call(this, evt, this._sortableInstance);
    };
    let copyOptions = _.extend({}, options);

    [ // Bind callbacks so that 'this' refers to the component
        'onStart', 'onEnd', 'onAdd', 'onSort', 'onUpdate', 'onRemove', 'onFilter', 'onMove'
    ].forEach((name) => {
        copyOptions[name] = (evt) => {
            if (name === 'onStart') {
                _nextSibling = evt.item.nextElementSibling;
                _activeComponent = this;
            } else if (name === 'onAdd' || name === 'onUpdate') {
                evt.from.insertBefore(evt.item, _nextSibling);

                let newState = {};
                let remoteState = {};
                let oldIndex = evt.oldIndex;
                let newIndex = evt.newIndex;
                let items = getModelItems(this);

                if (name === 'onAdd') {
                    let remoteItems = getModelItems(_activeComponent);
                    let item = remoteItems.splice(oldIndex, 1)[0];
                    items.splice(newIndex, 0, item);

                    remoteState[getModelName(_activeComponent)] = remoteItems;
                } else {
                    items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]);
                }

                newState[getModelName(this)] = items;

                if (copyOptions.stateHandler) {
                    this[copyOptions.stateHandler](newState);
                } else {
                    this.setState(newState);
                }

                (this !== _activeComponent) && _activeComponent.setState(remoteState);
            }

            setTimeout(() => {
                emitEvent(name, evt);
            }, 0);
        };

    });

    let domNode = ReactDOM.findDOMNode(this.refs[options.ref] || this);
    this._sortableInstance = Sortable.create(domNode, copyOptions);
}
componentWillReceiveProps(nextProps) {
    let newState = {};
    let model = getModelName(this);
    let items = nextProps[model];

    if (items) {
        newState[model] = items;
        this.setState(newState);
    }
}
componentWillUnmount() {
    this._sortableInstance.destroy();
    this._sortableInstance = null;
}

}

export default ReactSortable;
Sample code:

import ReactSortable from './ReactSortable';

class SortableContainer1 extends ReactSortable {
state = {
items: [0, 1, 2, 3, 4]
};
sortableOptions = {
group: 'shared'
};

render() {
    let items = this.state.items.map((text, index) => {
        return <li key={index}>{text}</li>;
    });

    return (
        <div>
            <ul ref="list">{items}</ul>
        </div>
    );
}

}

class SortableContainer2 extends ReactSortable {
state = {
items: [5, 6, 7, 8, 9]
};
sortableOptions = {
group: 'shared'
};

render() {
    let items = this.state.items.map((text, index) => {
        return <li key={index}>{text}</li>;
    });

    return (
        <div>
            <ul ref="list">{items}</ul>
        </div>
    );
}

}
—
Reply to this email directly or view it on GitHub.

@falzhobel Yes! It's basically the same but only to adopt changes for React v0.14 (e.g. ReactDOM).
It would be great if a ES6 version of react-sortable.jsx is available for developers to import to their projects. For example:

import ReactSortable from 'sortablejs/react-sortable';

A higher order React component (HOC) for Sortable is available at https://github.com/cheton/react-sortable.

You can run npm install --save react-sortablejs to install the package.

Awesome, will check it out soon, thank you!

This issue should be closed I guess?

Dear all! Meteor is moved to the separate repository. If this issue is still actual, please create it there once again and this rojects needs a maintainer:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

artaommahe picture artaommahe  Â·  4Comments

Webifi picture Webifi  Â·  3Comments

graemegeorge picture graemegeorge  Â·  4Comments

dwburdick picture dwburdick  Â·  3Comments

pageYe123 picture pageYe123  Â·  3Comments