Max Hladkiy Max Hladkiy - 4 months ago 85
React JSX Question

Trying to dispatch an action from the fabric.js event handler causes "Reducers may not dispatch actions."

I am developing an app using fabric.js and redux/react.

Shortly, what I want is when I add an object on the canvas it got selected and written into the app state.

And here is what I have for now - when I click on an object on the canvas the "object:selected" event
is fired and I dispatch the action to save the selected object into the state of my app.

fabricCanvas.on('object:selected', function(options) {
store.dispatch({
type: 'SET_SELECTED_OBJECT',
object: options.target
})
})


It works pretty well, but the fun starts when I try to automatically set object as selected when it's added to the canvas.

fabricCanvas.on('object:added', function(options) {
fabricCanvas.setActiveObject(options.target)
})


The function setActiveObject() fires the "object:selected" which is pretty logical and I expected my handler with dispatch to work but instead it grants me with

Uncaught Error: Reducers may not dispatch actions.


I know that there is a restriction to dispatch actions from reducers but I can't get why Redux thinks that I try to.

From the event handler I trigger another event which dispatch an action.
What I am doing wrong?

UPDATED:

Both handlers are defined in index.js right after the store and fabric defined.

import 'babel-polyfill';
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

import { clearSelectedObject, setSelectedObject, addObject } from './actions'
import initialState from './initialState.js'



let store = createStore(rootReducer, initialState, window.devToolsExtension && window.devToolsExtension())


render(
<div id="ocdc-holder">
<Provider store={store}>
<App />
</Provider>
</div>,
document.getElementById('root')
)

const fabricCanvas = new fabric.Canvas('canvas-lower')

fabricCanvas.on('object:added', function(options) {
fabricCanvas.setActiveObject(options.target)
})

fabricCanvas.on('object:selected', function(options) {
store.dispatch({
type: 'SET_SELECTED_OBJECT',
object: options.target
})
})


Here is my reducers.js file http://pastebin.com/SFHZ9jVr

Answer

Your handling ADD_TEXT action reducers.js file:

const newTextObj = new fabric.Text(action.payload.text)
fabricCanvas.add(newTextObj)
fabricCanvas.fire('object:selected')
return {
    ...state,
    objects: [
        ...state.objects,
        newTextObj
    ]
}

You shouldn't fire any events in reducers.

Fire them before/after dispatching ADD_TEXT action:

// Somewhere in component, or where you are dispatching `ADD_TEXT` action.
const newTextObj = new fabric.Text(action.payload.text)
fabricCanvas.add(newTextObj)
fabricCanvas.fire('object:selected')

Reducers should be free from side-effects. Everything reducer should do, is to return new state, using previous state and dispatched action.

You are getting this error, because you are firing 'object:selected' event in reducer, which produces action dispatching.