Skip to content

Instantly share code, notes, and snippets.

@krzysu
Last active February 16, 2022 20:47

Revisions

  1. Kris Urbas revised this gist Oct 19, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion App.js
    Original file line number Diff line number Diff line change
    @@ -66,7 +66,7 @@ class App extends Component {
    <div className="select-wrap">
    <SortableContainer>
    <Select
    options={options}
    options={items}
    value={this.state.selected.join(',')}
    multi={true}
    onChange={this.onChange}
  2. Kris Urbas created this gist Oct 19, 2016.
    81 changes: 81 additions & 0 deletions App.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    import React, { Component } from 'react';
    import Select from 'react-select'
    import SortableItem from './SortableItem';
    import SortableContainer from './SortableContainer';
    import update from 'react/lib/update';

    const items = [
    { value: '1', label: 'One' },
    { value: '2', label: 'Two' },
    { value: '3', label: 'Three' },
    { value: '4', label: 'Four' },
    { value: '5', label: 'Five' },
    { value: '6', label: 'Six' },
    { value: '7', label: 'Seven' },
    { value: '8', label: 'Eight' },
    { value: '9', label: 'Nine' },
    ];

    class App extends Component {
    constructor(props) {
    super(props);

    this.state = {
    selected: ['2','4','5','6'],
    items: items,
    };

    this.onChange = this.onChange.bind(this);
    this.valueRenderer = this.valueRenderer.bind(this);
    this.swapItems = this.swapItems.bind(this);
    }

    onChange(allSelected) {
    this.setState({
    selected: allSelected.map(item => item.value),
    });
    }

    valueRenderer(option, index) {
    return (
    <SortableItem
    index={index}
    className="sortable-item"
    swapItems={this.swapItems}
    >
    <span>{option.label}</span>
    </SortableItem>
    );
    }

    swapItems(dragIndex, hoverIndex) {
    const dragItem = this.state.selected[dragIndex];

    this.setState(update(this.state, {
    selected: {
    $splice: [
    [dragIndex, 1],
    [hoverIndex, 0, dragItem]
    ]
    }
    }));
    }

    render() {
    return (
    <div className="select-wrap">
    <SortableContainer>
    <Select
    options={options}
    value={this.state.selected.join(',')}
    multi={true}
    onChange={this.onChange}
    valueRenderer={this.valueRenderer}
    />
    </SortableContainer>
    </div>
    );
    }
    }

    export default App;
    15 changes: 15 additions & 0 deletions SortableContainer.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    import React, { Component, PropTypes } from 'react';
    import { DragDropContext } from 'react-dnd';
    import HTML5Backend from 'react-dnd-html5-backend';

    class SortableContainer extends Component {
    render() {
    return <span>{this.props.children}</span>;
    }
    }

    SortableContainer.propTypes = {
    children: PropTypes.oneOfType([PropTypes.element, PropTypes.array]),
    };

    export default DragDropContext(HTML5Backend)(SortableContainer);
    93 changes: 93 additions & 0 deletions SortableItem.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    import React, { Component, PropTypes } from 'react';
    import { findDOMNode } from 'react-dom';
    import { DragSource, DropTarget } from 'react-dnd';

    const ITEM_TYPE = 'sortable';

    class SortableItem extends Component {
    onMouseDown(event) {
    event.stopPropagation(); // important! as react-select preventsDefault on mouseDown event, preventing also dragging
    }

    render() {
    const { isDragging, connectDragSource, connectDropTarget, className, children } = this.props;
    const opacity = isDragging ? 0 : 1;

    return connectDropTarget(connectDragSource(
    <span
    className={className}
    style={{ opacity }}
    onMouseDown={this.onMouseDown}
    >
    {children}
    </span>
    ));
    }
    }

    SortableItem.propTypes = {
    // props from react-dnd
    connectDragSource: PropTypes.func.isRequired,
    connectDropTarget: PropTypes.func.isRequired,
    isDragging: PropTypes.bool.isRequired,
    // props provided by parent
    index: PropTypes.number.isRequired,
    children: PropTypes.element.isRequired,
    swapItems: PropTypes.func.isRequired,
    className: PropTypes.string,
    };


    const source = {
    beginDrag(props) {
    return {
    index: props.index,
    };
    },
    };

    const target = {
    hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;

    // implement your own behaviour, below example taken from http://gaearon.github.io/react-dnd/examples-sortable-simple.html
    if (dragIndex === hoverIndex) {
    return;
    }

    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
    const clientOffset = monitor.getClientOffset();
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;

    if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
    return;
    }

    if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
    return;
    }

    // when you want to swap items run
    props.swapItems(dragIndex, hoverIndex);

    // note: we're mutating the monitor item here!
    monitor.getItem().index = hoverIndex;
    },
    };

    function mapDropConnectToProps(connect) {
    return {
    connectDropTarget: connect.dropTarget(),
    };
    }

    function mapDragConnectToProps(connect, monitor) {
    return {
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging(),
    };
    }

    export default DropTarget(ITEM_TYPE, target, mapDropConnectToProps)(DragSource(ITEM_TYPE, source, mapDragConnectToProps)(SortableItem));