Skip to content

Instantly share code, notes, and snippets.

@markbiek
Last active February 4, 2018 20:00
Show Gist options
  • Save markbiek/f807dd8235a7fa9c68a6f9862bf4790d to your computer and use it in GitHub Desktop.
Save markbiek/f807dd8235a7fa9c68a6f9862bf4790d to your computer and use it in GitHub Desktop.
HTML5 drag-and-drop with React + Redux
/*
Note: This is organized in a strange way so it can run as a CodePen (https://codepen.io/markbiek/pen/KQzoVM?editors=0110)
See https://gist.github.com/markbiek/d5942ae8c43757719db5422793e10f67 for styles
*/
/* Data functions */
/***************************************/
const { fromJS, mergeDeep } = Immutable;
/* Reducers */
/***************************************/
/**
* This is the format of our initial data structure. Once we get earthquake data, we'll set data to it.
* The state is an ImmutableJS object to make updating it easier.
*/
const testInitialState = fromJS({
draggable: { 1: {id: 1}, 2: {id: 2}, 3: {id: 3} },
dropped: {}
});
/**
* This reducer handles the 'GOT_DATA' message and updates our state.
*/
const testReducer = function (state = testInitialState, action) {
switch (action.type) {
case 'DND_MOVE_TO_DRAGGABLE':
{
let dropped = state.get('dropped').toJS();
if (dropped.hasOwnProperty(action.id)) {
let draggable = state.get('draggable').toJS();
draggable[action.id] = { ...dropped[action.id] };
delete dropped[action.id];
return state
.set('draggable', fromJS(draggable))
.set('dropped', fromJS(dropped));
}
return state;
}
case 'DND_MOVE_TO_DROPPED':
{
let draggable = state.get('draggable').toJS();
if (draggable.hasOwnProperty(action.id)) {
let dropped = state.get('dropped').toJS();
dropped[action.id] = { ...draggable[action.id] };
delete draggable[action.id];
return state
.set('dropped', fromJS(dropped))
.set('draggable', fromJS(draggable));
}
return state;
}
default:
return state;
}
}
/* Store */
/***************************************/
const { combineReducers, createStore} = Redux;
const { Provider } = ReactRedux;
const { connect } = ReactRedux;
//If we add any other reducers, we need to add them here so they'll be part of the store
const reducers = combineReducers({
testState: testReducer
});
const store = createStore(reducers);
const { dispatch } = store;
/* Index */
/***************************************/
/**
* App component
* Our main app component. It calls the function to load earthquake data when it's about to mount.
*/
class App extends React.Component {
render() {
return (
<Dnd />
);
}
}
class Dnd_ extends React.Component {
constructor(props) {
super(props);
this.state = {
dragged_id: null
};
}
render() {
const { draggable, dropped } = this.props;
return (
<div>
<div className="grid draggable"
onDragOver={e => {
e.preventDefault();
}}
onDrop={e => {
dispatch({ type: 'DND_MOVE_TO_DRAGGABLE', id: this.state.dragged_id});
}}
>
{
Object.keys(draggable).map(key => {
let box = draggable[key];
return (
<div className="box"
key={box.id}
data-id={box.id}
draggable="true"
onDragStart={e => {
this.setState({
dragged_id: e.target.dataset.id
});
}}
>{box.id}</div>
)
})
}
</div>
<div className="grid dropped"
onDragOver={e => {
e.preventDefault();
}}
onDrop={e => {
dispatch({ type: 'DND_MOVE_TO_DROPPED', id: this.state.dragged_id});
}}
>
{
Object.keys(dropped).map(key => {
let box = dropped[key];
return (
<div className="box"
key={box.id}
data-id={box.id}
draggable="true"
onDragStart={e => {
this.setState({
dragged_id: e.target.dataset.id
});
}}
>{box.id}</div>
)
})
}
</div>
</div>
);
}
}
const dndComponentMapState = function(store) {
return {
draggable: store.testState.toJS().draggable,
dropped: store.testState.toJS().dropped
};
};
const Dnd = connect(dndComponentMapState)(Dnd_);
/* Render */
/***************************************/
//Render the app to the DOM
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('app'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment