Uncaught error reducers may not dispatch actions

I haven't been able to replicate this in an isolated environment, but it's an error which keeps getting picked up from our error reporter. We have two websockets which asynchronously push d...

I agree this should be impossible. However I managed to recreate this using Chrome 75.0.3770.100. I’m trying to get this to happen again but I haven’t managed to get it to happen again. But I would be interested if someone else can get it to happen again. I burrowed the prime number functions from https://www.npmjs.com/package/prime-number

/**
 * Benchmark a primality algorythm
 *
 * @param {Function} isPrime
 *
 * @returns {Function} checkPrimality (from, to)
 */
function primeBenchMark(isPrime) {
  /**
   * Primality check on given range
   *
   * @param {Number} from
   * @param {Number} to
   */
  return function checkPrimality(from, to) {
    console.time('primality benchmark');

    var countPrimes = 0;

    for (var i = from; i <= to; i++) if (isPrime(i)) countPrimes++;

    console.log(`Found ${countPrimes} primes`);

    console.timeEnd('primality benchmark');
  };
}

/**
 * Check if a number is prime
 *
 * @param {Number} n
 *
 * @returns {Boolean} isPrime
 */
function primeNumber(n) {
  if (n === 2) return true; // 2 is a special case
  if (n % 2 === 0) return false;
  for (var i = 3; i <= Math.sqrt(n); i = i + 2) {
    if (!primeNumber(i)) continue; // <-- recursion here
    if (n % i === 0) return false;
  }
  return true;
}

let lock = false;
let count = 0;

/**
 * A function that checks javascript is single threaded, by setting a lock, and computing a prime numbers, given js is
 * single threaded each iteration should finish executing before the next one begins
 *
 * @return {void}
 */
function lockedFunction() {
  count++;
  console.info(`Started ${count}`);

  if (lock) {
    throw new Error(`Not single threaded ${lock} hasn't finished executing before ${count} executed`);
  }

  try {
    lock = count;
    primeBenchMark(primeNumber)(10000, 500000);
  } finally {
    lock = false;
  }

  console.info(`Ended ${count}`);
};


/**
 * Runs a given function many times
 *
 * @param {function} func the function to run
 * @param {number} times to run the calculation
 * @param {number} delay ms delay time between executions
 *
 * @return {void}
 */
function runMany(func, times, delay) {
  let count = 0;

  function runCycle() {
    func();
    if (count < times) {
      count++;
      setTimeout(runCycle, delay);
    }
  };

  runCycle();
};

/**
 * Runs a function that computes prime numbers many times
 *
 * @param {number} times to run the calculation
 * @param {number} delay ms delay time between executions
 *
 * @return {void}
 */
function lockTest(times = 100, delay = 50) {
  runMany(lockedFunction, times, delay);
};

Running lockTest(); a few times in the console I got this to finally happen

Started 325
index.js?m=1562193618:155900 Started 326
index.js?m=1562193618:155900 Uncaught Error: Not single threaded 325 hasn't finished executing before 326 executed
    at lockedFunction (index.js?m=1562193618:155900)
    at runCycle (index.js?m=1562193618:155907)
lockedFunction @ index.js?m=1562193618:155900
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
runMany @ index.js?m=1562193618:155907
lockTest @ index.js?m=1562193618:155914
(anonymous) @ VM293424:1
index.js?m=1562193618:155900 Started 327

Preface

It’s been a while since using React to write requirements. Many problems have been encountered in the middle. Most of them can barely cope with Baidu and Googled. There are a lot of learning and communication groups that I don’t understand. Guys ask for advice. Today, when I routinely use dva to write requirements, I encountered a problem. Baidu hasn’t answered for a long time, and then I followed the error message and finally found the source of the problem.

error

Below is a screenshot of the error
image
The error is actually obviousUncaught Error:Reducer may not dispatch actions. The error message is increateStore.js:160Where to go, this is where the redux source code creates store objects. I wonder if it is impossible to study the source code.

createStore.js

The following is the context of the error content


  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

You can see that the specific code for the error is

if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
}

whenisDispatchingfortrueWill throw this error, and then you can see when everydispatchWill go down whentrywillisDispatchingSet totruebycurrentReducerwithaction, Update the currentstore tree,only atfinallyWillisDispatchingSet tofalse, So when againdispatchBecause the state is stilltrue, So this error was thrown.

the reason

Internet Baidu thisisDispatchingThe meaning of this global state, the explanation given on the Internet is:

The globally defined isDispatching is used to lock the changing store tree; that is, the next change is allowed only after the store tree is changed this time, to avoid the problem of unsynchronized results when the store tree responds to multiple changes ; But in fact, this way of writing also determines that the current store tree can only respond to synchronous changes (asynchronous changes need to be implemented by adding middleware)

to sum up

There will always be problems. If you encounter problems you haven’t seen before, you must learn Baidu (to move bricks for Baidu). In order to move bricks better, you should not be afraid of source code such as redux. Learn to try to read.

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

Я разрабатываю приложение, используя fabric.js и redux/реагирую.

В скором времени, я хочу, когда я добавляю объект на холсте, который он выбрал и записал в состояние приложения.

И вот то, что у меня есть сейчас — когда я нажимаю объект на холсте, происходит событие «объект: выбран», и я отправляю действие для сохранения выбранного объекта в состояние моего приложения.

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

Он работает очень хорошо, но забава начинается, когда я пытаюсь автоматически установить объект как выбранный, когда он добавлен в холст.

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

Функция setActiveObject() запускает «объект: выбран», что довольно логично, и я ожидал, что мой обработчик с диспетчером будет работать, но вместо этого он предоставляет мне

Uncaught Error: Reducers may not dispatch actions.

Я знаю, что существует ограничение на отправку действий с редукторов, но я не могу понять, почему Redux думает, что я пытаюсь.

Из обработчика событий я запускаю другое событие, которое отправляет действие. Что я делаю неправильно?

ОБНОВЛЕНО:
Оба обработчика определены в index.js сразу после определения магазина и ткани.

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

Вот мой файл reducers.js http://pastebin.com/SFHZ9jVr

Помогите пожалуйста, вообще не понимаю откуда ошибка. Сижу и голову ломаю.
Использовала Create React App для создания приложения (из документации).
Хочу сначала подключить и более менее ориентировать в redux, а потом присоединить react-router и react-router-redux.
Просто для теста хотела сделать добавление нового юзера (action).
Протянула action в нужный компонент и тут ошибка:
Uncaught Error: Actions may not have an undefined «type» property. Have you misspelled a constant?
Я по разному протягивала action, пробовала bindActionCreators, по другим примера писала configureStore, не понимаю.
Помогите пожалуйста, в чём ошибка?
Все файлы ниже

appReducer.js

import {ADD_USER} from '../constans/appConstants';

const initialState = {
    users: [
        {
            name: "Nastya",
            age: 20
        }
    ]
};

const appReducer = (state = initialState, action) => {

    switch(action.type){

        case ADD_USER: {
            let newUser = {
                name: action.name,
                age: action.age
            };
            return {
                ...state,
                users: state.users.push(newUser)
            }
        }

        default:
            return {
                ...state
            }
    }
};

export default appReducer;

Написала для добавления action

import {ADD_USER} from '../constans/appConstants';

export const addUser = (name, age) => {
    return {
        type: ADD_USER,
        name: name,
        age: age
    }
};

Файл configureStore:

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers/index';

const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);

const configureStore = initialState => (
    createStoreWithMiddleware(rootReducer, initialState)
);

export default configureStore;

Точка входа в приложение (index.js):

import registerServiceWorker from './registerServiceWorker';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import AppContainer from './containers/AppContainer';
import configureStore from './store/configureStore';

const render = (Component) => {
    ReactDOM.render(
        <Provider store={configureStore()}>
            <Component />
        </Provider>,
        document.getElementById('root'),
    );
};

render(AppContainer);

registerServiceWorker();

Контейнер AppContainer:

import React from 'react';
import {connect} from 'react-redux'
import {addUser} from '../actions/appActions'

import App from '../components/App';

const Application = props => <App {...props} />;

const mapStateToProps = state => ({
    appProps: state.appReducer
});

export default connect(mapStateToProps, {
    addUser
})(Application);

Компонент App:

import React, {Component} from 'react';

import AddUserForm from './AddUserForm';

class App extends Component {
    render() {
        let {addUser} = this.props;
        return (
            <div>
                <h1>Hello World</h1>
                <hr/><br/><br/>
                <AddUserForm addUser={addUser} />
            </div>
        )
    }
}

export default App;

И компонент AddUserForm:

import React, {Component} from 'react';

class AddUserForm extends Component {

    constructor(props){
        super(props);
        this.addNewUser = this.addNewUser.bind(this);
    }

    addNewUser() {
        let {addUser} = this.props;
        let name = this.inputUserName.value;
        let age = this.inputUserAge.value;
        addUser(name, age);
    }

    render() {
        return (
            <form>
                <input ref={input => {this.inputUserName = input}} type="text" name="name" placeholder="Name new user"/>
                <input ref={input => {this.inputUserAge = input}} type="text" name="age" placeholder="Age new user"/>
                <button onClick={this.addNewUser} type="button">Add User</button>
            </form>
        )
    }
}

export default AddUserForm;

Я разрабатываю приложение с использованием fabric.js и redux / response.

Вкратце, я хочу, чтобы я добавлял объект на холст, который был выбран и записан в состояние приложения.

И вот что у меня есть на данный момент — когда я нажимаю на объект на холсте, запускается событие «object: selected», и я отправляю действие для сохранения выбранного объекта в состоянии моего приложения.

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

Он работает довольно хорошо, но самое интересное начинается, когда я пытаюсь автоматически установить объект как выбранный, когда он добавляется на холст.

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

Функция setActiveObject () запускает «object: selected», что довольно логично, и я ожидал, что мой обработчик с диспетчеризацией сработает, но вместо этого он предоставляет мне

Uncaught Error: Reducers may not dispatch actions.

Я знаю, что существует ограничение на отправку действий из редукторов, но я не могу понять, почему Redux думает, что я пытаюсь это сделать.

Из обработчика событий я запускаю другое событие, которое отправляет действие. Что я делаю не так?

ОБНОВЛЕНО:
Оба обработчика определены в index.js сразу после определения хранилища и структуры.

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

Вот мой файл reducers.js http://pastebin.com/SFHZ9jVr

1 ответ

Лучший ответ

Ваш обрабатывающий ADD_TEXT файл действия reducers.js:

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

Вы не должны запускать какие-либо события в редукторах.

Запустить их до / после отправки действия ADD_TEXT:

// 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')

Редукторы не должны иметь побочных эффектов. Все, что должен делать редуктор, — это возвращать новое состояние, используя предыдущее состояние и отправленное действие.

Вы получаете эту ошибку, потому что запускаете событие 'object:selected' в reducer, которое производит диспетчеризацию действий.


1

1ven
5 Авг 2016 в 16:20

Потихоньку погружаясь в redux и начиная осозновать правила мироздания его идеологии, многие приходят к следующему вопросу: «А куда я должен прилепить свои запросы к серверу?». В ответ на этот вопрос, вас знакомят с понятием ‘middleware’ функций, что на русский можно перевести как усилитель. И если в то, зачем эти функции нужны и почему такие вещи как запросы к API нужно именно здесь понять можно. А вот взгляд на синткасис объявления ‘middleware‘ у человека недавно познакомившейся со всей этой кухней react, redux, es6 и прочего веселья вызывает ужас вперемешку с криками ‘ЧООО?’ и расшатывание только восстановившейся психики после привыкания к предыдущим новым терминам и концепциям. Давайте взглянем на это и попытаемся спуститься глубже, чтобы понять как устроена middleware.

const middleware = store => next => action => {
}

Да, да господа. Middleware — ф-ция, которая принимает store, возвращает ф-цию которая принимает dispatch, которая возвращает ф-цию которая принимает action. Заправлено это всё лямбда синтаксисом из ES6. Зачем? Источники говорят, что это чудо пришло к нам из функционального программирования и лежащим в его основе мат. аппарата. Можно почитать здесь.

Мы получили, что middleware функция  по факту принимает 3 аргумента: store, next и action. Теперь предлагаю взглянуть в код redux и посмотреть, как там всё устроено.

Структура библиотеки выглядит так:

Redux представляет нам api из 5 функций: createStore, combineReducers, applyMiddleware, bindActionCreators, compose.  Сегодня я хочу остановиться лишь на рассмотрении трех из них (подчеркнуты).

Начнём с createStore, т.к. она является базовой и в примерах создания redux приложения перво-наперво вы сталкиваетесь именно с ней. Ниже вы можете увидеть часть кода, которая будет нас интересовать.

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

createStore как ясно из названия, возвращает хранилище приложения. Аргументами функции могут являться 3 параметра: редьюсер, начальное состояние приложение и enchancer (по факту, эта функция которая возвращается  после вызова applyMiddleware). Функция начинается с обработки входных аргументов и выяснения того, имеются ли в наличии какие-либо ‘middleware’. Рассмотрены также ситуация, что начальное состояние не было укзаано, а вторым аргументом передана функция, тогда второй аргумент считается что это и есть ‘усилитель’. Если усилитель имеется, то вместо хранилища возвращается результат выполнения этой функции. Т.е. поток управления передаётся на неё.

return enhancer(createStore)(reducer, preloadedState)

Иначе формируется объект состояния приложения. В котором нас интересуют лишь свойства dispatch и getState.

 return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

Принцип работы getState вы, надеюсь, поймете сами, а вот dispatch рассмотрим вместе. В концепции redux dispatch — это функция, которая принимает событие, передает его редьюсеру, получает от него новый объект и посылает сигнал всем, кто подписался на изменение состояния.

В общих чертах createStore мы рассмотрели. Теперь можно обратиться к коду applyMiddleware

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware принимает массив middleware функций, формат которых мы рассмотрели вначале статьи, а возвращает функцию усилитель (в коде createStore она была параметром enchance). Здесь же мы видим, что applyMiddleware возвращает лямбду в лямбде. Я предлагаю, попытаться абстрагироваться от такого подхода и представлять синтаксис (x) => (y) => {}, как функцию принимающую 2 аргумента x, y.

Соответственно, наша функция возвращает функцию, которая принимает 4 аргумента. Если вернуться назад и посмотреть, место вызоыва этой функции, то можно увидеть, что передаются в ней на самом деле 3 аргумента.

return enhancer(createStore)(reducer, preloadedState)

Enchance — остается пустой. И после вызова функции createStore из applyMiddlware мы получим просто объект хранилища, который будет содержать описанный выше dispatch и другие менее интересные нам вещи.

 const store = createStore(reducer, preloadedState, enhancer)

Следующая наша задача — преобразовать исходный dispatch, который формирует новый state и сигнализирует об изменеии в цепочку вызовов, обновления состояние в котором происходит в последнюю очередь.

    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

Выше можно увидеть, практическое применение разбиения функции от трех аргументов на три функции, каждая из которых принимает по аргументу. Объект middlewareAPI по сути является аргументом store и с помощью функции map, всем middleware функциям проставляется этот аргумент. По факту, сейчас была произведено отложенное проставление одного из аргументов, а именно store во все функции в массиве.

dispatch = compose(...chain)(store.dispatch)

.Следующим этапом является преобразование dispatch в цепочку функций. Функция compose же выглядит так:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

Чтобы понять данный код, стоит воспользоваться описанием функции reduce, также чтобы увидеть как dispatch становится списком функций завязанных друг с другом через next, можно повыполняв пошагово код инициализации store и дойдя до этого участка. Каждая функция  должна вызывать next(action), чтобы не прервать цепочку. В конце можно увидеть, что результатом работы createStore с указанным 3 аргументом с вызывом в нём applyMiddleware будет объект хранилища, но с преобразованным dispatch

 return {
      ...store,
      dispatch
    }

На сегодня я вас покину. Удачи!

Понравилась статья? Поделить с друзьями:
  • Uncaught error no url provided dropzone
  • Uncaught error no element is specified to initialize perfectscrollbar
  • Uncaught error map container is already initialized
  • Uncaught error createroot target container is not a dom element
  • Uncaught error invalidstateerror the connection has not been established yet