redux state gets -improperly- updated before reducers is called (w/ ReactDnD)

Tags: , , , ,



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 })
}

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.



Source: stackoverflow