Edit: the bug was is a separated helper function that was mutating the state (not displayed in the post).
I’m experimenting with ReactDnD to create a sortable image grid via drag and drop. I’ve been following this tutorial 1 and trying to implement it with redux instead of React Context.
The issue that I’m having is that my props don’t get updated after I re-arrange the images. I have been debugging the reducers and noticed that the state gets somehow updated before the reducer has the chance to do so (which would trigger mapStateToProps to reload my component with the updated state). The problem though it that I have no idea why that happens. I have the feeling that since ReactDnD is also using Redux, it’s somehow causing this.
Here are the different parts:
Index.js
export const store = createStore(reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <DndProvider backend={HTML5Backend}> <App /> </DndProvider> </Provider>, document.getElementById('root') )
App.js (parent component of DroppableCell and DraggableItem)
class App extends React.Component { componentDidMount() { this.props.loadCollection(imageArray) } render() { return ( <div className='App'> <div className='grid'> {this.props.items.map((item) => ( <DroppableCell key={item.id} id={item.id} onMouseDrop={this.props.moveItem} > <DraggableItem src={item.src} alt={item.name} id={item.id} /> </DroppableCell> ))} </div> </div> ) } } const mapStateToProps = (state) => { return { items: state.items } } export default connect(mapStateToProps, { moveItem, loadCollection, })(App)
DroppableCell (calling the action creator from parent component)
import React from 'react' import { useDrop } from 'react-dnd' const DroppableCell = (props) => { const [, drop] = useDrop({ accept: 'IMG', drop: (hoveredOverItem) => { console.log(hoveredOverItem) props.onMouseDrop(hoveredOverItem.id, props.id) }, }) return <div ref={drop}>{props.children}</div> } export default DroppableCell
DraggableItem
import React from 'react' import { useDrag } from 'react-dnd' const DraggableItem = (props) => { const [, drag] = useDrag({ item: { id: props.id, type: 'IMG' }, }) return ( <div className='image-container' ref={drag}> <img src={props.src} alt={props.name} /> </div> ) } export default DraggableItem
Reducer
import { combineReducers } from 'redux' const collectionReducer = (state = [], action) => { // state is already updated before the reducer has been run console.log('state:', state, 'action: ', action) switch (action.type) { case 'LOAD_ITEMS': return action.payload case 'MOVE_ITEM': return action.payload default: return state } } export default combineReducers({ items: collectionReducer, })
The action creator
export const moveItem = (sourceId, destinationId) => (dispatch, getState) => { const itemArray = getState().items const sourceIndex = itemArray.findIndex((item) => item.id === sourceId) const destinationIndex = itemArray.findIndex( (item) => item.id === destinationId ) const offset = destinationIndex - sourceIndex //rearrange the array const newItems = moveElement(itemArray, sourceIndex, offset) dispatch({ type: 'MOVE_ITEM', payload: newItems }) }
Advertisement
Answer
found the bug – unfortunately was outside the code posted as I thought it was a simple helper function. I realised I was using the ‘splice’ method to rearrange the imageArray, and therefore mutating the state.