Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Globalized Redux Selector In Side Effect Creates Circular Dependency

I'm storing form data in redux store after normalizing it using normalizr. When I submit the form, I get the denormalized data using selectors inside my thunk and then send it to the server. The flow goes the following way:

rootReducer -> localReducer -> action/actionCreator -> rootReducer

In rootReducer file, the root reducer composes localReducer and contains the globalized selector to be used later in the thunk. The localReducer file imports actions from the actions file which contains the action creators too. The thunk action creator returns a thunk which does the api call using data retrieved by the selector in rootReducer file, hence the circular dependency.

Webpack is not handling well this circular dependency. I got a runtime Uncaught TypeError: Cannot read property 'JOB_FORM_RESET' of undefined error at the localReducer -> action/actionCreator level:

const jobsForm = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.JOB_FORM_RESET:

Any thoughts on how to solve this?

Edit

ActionTypes being evaluated to undefined works as specced. ActionTypes is located in the action/actionCreator file whose execution didn't finish when it was first imported by localReducer because it started immediately importing the rootReducer. In order to avoid the infinite loop, an unfinished copy of action/actionCreator (where ActionTypes is evaluated to undefined) is given to localReducer.

The solution is to separate the actions and action creators by putting them in two different files. This will remove the cyclic dependency as shown in the following flow:

rootReducer -> localReducer -> action
actionCreator -> rootReducer

The weird thing for me is that grouping action and action creators has been advocated in redux guidelines since too long and separating them in two files feels less natural.

In addition, this cyclic issue doesn't appear in redux-saga model:

rootReducer -> localReducer -> action/actionCreator
saga -> api -> rootReducer

I'm used to this model but can't believe that redux-thunk model doesn't solve this issue. In other words, it doesn't seem fair to say that the cyclic issue is an inherent side effect of the redux-thunk model. Am I missing something here?

You can find an MCVE in this repo. The error is different but it's the same principle, it was caused by a cyclic dependencies caused by the following import in src/Users/actions.js file:
import { getSelectedUsers } from '../reducer';

The occurred error is No reducer provided for key "users". Just comment the above import and the error will disappear.

As I describe above, this works as specced, my concern is that the redux-thunk model doesn't handle this use case. In addition, putting actions and action creators both in the same file and then wait for a cyclic dependencies issue to occur to separate them doesn't seem a scalable solution.

like image 327
ahmehri Avatar asked Jan 25 '26 12:01

ahmehri


1 Answers

The solution is quite simple: Extract actionTypes to separate file and import it in actions.js and reducer.js

actionTypes.js file:

export const SELECT_USER = 'SELECT_USER'; export const POST_USERS = 'POST_USERS';

You can import all actions at once like this

import * as actionTypes from './actionTypes.js'

Problem solved here:

https://github.com/svitekpavel/redux-thunk-globalized-selectors-cyclic-dependencies/commit/1c7f04fc5c1d4e4155891428138f8cb00412655e

Two more recommendations:

  1. Extract selectors to separate file
  2. Extract "effects" (postUsers) to effects.js

The second recommendation comes from experience, that these functions (side effects) that tutorials of redux-thunks keep in actions.js are actually side effects and not action creators.

If you were to use Redux-Saga, you would quickly realize that decoupling business logic (and side effects) from action creators is a good thing.

Also, they are two separate things :-)

like image 69
Pavel Avatar answered Jan 27 '26 00:01

Pavel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!