Reorder an item on the dashboard list (pure React Drag and Drop example)

We will implement the reordering in a proper React way without using any external reordering components or libraries.

Let's start from the reducers and actions that are related to the reordering feature:

Changes on the server if the user has provided correct or incorrect information(you can click the diffs image to make it larger):
src/routes/Dashboard/modules/dashboard.js

926_reducer_action_reorder

An explanation for the code from the diffs - you need to understand what we will send as a payload (const { end: nextPosIndex, start: currPosIndex } = action.payload) in the function below:


  [DASHBOARD_REORDER_ITEM]: (state, action) => {
    const { end: nextPosIndex, start: currPosIndex } = action.payload
    const element = state.dashboardItems[currPosIndex]
    let dashboardItems = [
      ...state.dashboardItems.slice(0, currPosIndex),
      ...state.dashboardItems.slice(currPosIndex + 1)
    ]

    dashboardItems.splice(nextPosIndex, 0, element)

    return {
      ...state,
      dashboardItems
    }
  }

We will send an object with the following start and end properties as on the example below:

// just an example schema of
// const reorder = action.payload
// so you can see what values are sent to the DASHBOARD_REORDER_ITEM

this.props.reorderItem({
  start: this.state.draggedItemIndex,
  end: droppedItemId
})

The start is a set of numbers in the dashboardItems array. The end property is an order number of a dropped-on-the-item h4 block (the id of an item that a user dropped on the dragged item). The rest of the code shall be self-explanatory.

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/containers/DashboardContainer.js

927_dashboardContainer1

Changes in (you can click the diffs image to make it larger):
src/routes/Dashboard/components/Dashboard.js

927_dashboardContainer1

Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/Dashboard.js

927_dashboardContainer1

From the above diffs we can find new functions related to the DnD:

handleOnDragStart = ev => this.setState({ draggedItemIndex: ev.target.id })

handleOnDragOver = ev => {
  ev.preventDefault()
  ev.dataTransfer.dropEffect = 'move'  // See the section on the DataTransfer object.
  // You can add here more logic if required
}

handleOnDrop = ev => {
  const droppedItemId = ev.currentTarget.id
  if (this.state.editedItemIndex === null) {
    this.props.reorderItem({
      start: this.state.draggedItemIndex,
      end: droppedItemId
    })
  }
  this.setState({ draggedItemIndex: null })
}

The most important part to understand this code above is that the h4 block has an id as a number (check the code from the src/components/Dashboard/Dashboard.js which is listed below) and that number is an order in the new array that is kept in the dashboard reducer.

Based on that assumption, we can use:

handleOnDragStart = ev => this.setState({ draggedItemIndex: ev.target.id })

to set the start item. Below you can see how we get the final end newDashboardItems number

handleOnDrop = ev => {
  const droppedItemId = ev.currentTarget.id
  if (this.state.editedItemIndex === null) {
    this.props.reorderItem({
      start: this.state.draggedItemIndex,
      end: droppedItemId
    })
  }
  this.setState({ draggedItemIndex: null })
}

The condition this.state.editedItemIndex === null prevents reorder when any item in edit

The rest of the code should be self-explanatory (if something is unclear, please contact us at kamil.przeorski@gmail.com).

Changes in (you can click the diffs image to make it larger):
src/components/Dashboard/ListJSX.js

929_changes_in_dashboard_map

const items = dashboardItems.map((item, i) => {
  const itemJSX = activeIndex === i
    ? <p><b><u>{item.label}</u></b></p>
    : <p>{item.label}</p>

  return (
    <h4
      onDragOver={onDragOver}
      onDragStart={onDragStart}
      onDrop={onDrop}
      draggable='true'
      id={i}
      key={i}
      onClick={onClick(i)}
      className='dashboard-list-item' >{itemJSX}</h4>
  )
})

The last part is to add callbacks (onDragOver, onDragStart and onDrop), modify style and add the id={i} (so we can take a h4's id as the information required to make DnD works).

This is how the app will behave after we have made all of our changes:

930_dashboard_reordering

Source of the commit's screenshots:
https://github.com/ReactPoland/reactjs-redux-tutorial/commit/chapter-6

React Poland 2017