Using Globalized Redux Selector In Side Effect Creates Circular Dependency - reactjs

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.

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:
Extract selectors to separate file
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 :-)

Related

Middleware for RTK-Query API at reducerPath Error - "only" while running RTL tests

I am trying to migrate my redux store to RTK and RTK query - and making my RTL tests work with it
I am going for a gradual re-write - transforming each reducer 1 by 1.
I have re-created my store used in the application - replacing createStore with a configureStore - and added the middleware for api handling like this:
import { vendorPaymentsApiSlice } from './vendorPaymentsAPISlice';
const rootReducer = combineReducers({
users: userReducer,
[vendorPaymentsApiSlice.reducerPath]: vendorPaymentsApiSlice.reducer
})
//vendorPaymentsApiSlice.reducerPath is set to "vendorpaymentsApi"
const store = configureStore({
reducer: rootReducer,
middleware: [
...getDefaultMiddleware(),
sagaMiddleware,
vendorPaymentsApiSlice.middleware,
],
});
This setup is working fine in the real application. The middleware linking for api is working as expected.
For my tests i am using a mock store - which replaces history with mockHistory but it is mostly the same code- same rootReducer and same middleware array.
In my tests I have written a msw interceptor for the api call -
and the interceptor is getting called:
but as soon as that happens - I am getting this error message:
Warning: Middleware for RTK-Query API at reducerPath "vendorpaymentsApi" has not been added to the store.
Features like automatic cache collection, automatic refetching etc. will not be available.
Because of this - the data setting in redux from the api is failing .
I tried many variations of adding the middleware after reading this and tried
getDefaultMiddleware().concat([
sagaMiddleware,
vendorPaymentsApiSlice.middleware,
])
but it's still not working.
Wanted some help to understand why the linking of this api is not happening with the tests.
Thanks a lot for reading this
the correct way of setting up your middleware would be
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(sagaMiddleware).concat(vendorPaymentsApiSlice.middleware),
but I'm honestly not 100% sure if that's your issue. Are you really using this store in your tests (you should!) and not some kind of mock store?
You have given the api a reducerPath of "vendorpaymentsApi" and then you mount it in your store as vpApi.
Either change the reducerPath option to vpApi or change vpApi: vpApi.reducer to vendorpaymentsApi: vpApi.reducer
I tried to recreate this error in a simpler repo here
The problem was not in the middleware declaration in configure store - but in another pattern which was being used.
Pattern: "Instead of creating a new store every time while testing a redux connected component as described here - use a store singleton and dispatch an action to reset the store to initial data passed from the test"
As shown in this file
But the problem was that I had forgotten to set the api reducer in my default empty state which was being used as base - before shallow merging the passed data:
I had ommitted the "api" key in this which was causing this error - and once I added it back - the error went away.

How to structure data reducers in Redux-toolkit with multiple apis

I'm beginner developer developing an application with react using typescript and redux-toolkit (mainly with createSlice, createAsyncThunk).
I'm still confused on how to structure/manage data with multiple data apis
for example :
there are 3 endpoints from domain.
/products,
/photos,
/info
I've created each slice according to the endpoint
productSlice,
PhotoSlice,
infoSlice
each slice has reducer, createAsyncThunk, extraReducers,
then combined all reducers like below
const rootReducer = combineReducers({
product: productSlice.reducer,
photo: photoSlice.reducer,
info: infoSlice.reducer
});
each slice has reducer, createAsyncThunk, extraReducers,
then combined all reducers like below
Actually I've spent a lot of time to find a right way of structuring data.
then I've come across this code that other developer did like below
const rootReducer = combineReducers({
getProduct: getProductSlice.reducer,
createProduct: createProductSlice.reducer,
updateProduct: updateProductSlice.reducer,
deleteProduct: deleteProductSlice.reducer,
getPhotoDetails: getPhotoDetailsSlice.reducer,
updatePhotoDetails: updatePhotoDetailsSlice.reducer,
deletePhotoDetails: deletePhotoDetailsSlice.reducer,
getInfo: getInfoSlice.reducer,
createInfo: createInfoSlice.reducer,
updateInfo: updateInfoSlice.reducer,
deleteInfo: deleteInfoSlice.reducer,
});
I'm actually not sure (neither is he )
which way is better of structuring Reducers and data?
or if you show me other way or examples of structuring reducer and data,
It would be really nice.
Since the last version, Redux Toolkit ships with RTK Query, which would replace all those reducers with one auto-generated one. That might help you reduce your code quite a bit, give it a look.

errors happen when I import another module's actions

I am writing a single-paged application. Since this application updates pages on rootApp, some actions of rootApp are planned to be used by other pages, e.g. redirectPageTo. I directly use import and export to try to directly dispatch the action of RootApp, but the compiling error happens. The error is something undefined, but actually it's well defined in the same file. Is there any correct way to do this? Any ideas are welcome.
//this page is the page other than RootApp; here is the LoginPage reducer
import {redirectPageTo} from './RootAppAction.jsx';//import actions, with this line, errors like defaultState undefined happens
const defaultState={(some states declaration)};
export function updateLoginPageState(state={},action) {
...
dispatch(redirectPageTo("HOMEPAGE"));//use other module's action. Can I use it in this way?
}
I find out the root cause may relate to that I put the reducer and the action codes in the same file.

Redux - external libs and "React" in reducer?

I have simple Redux reducer, but for some actions I need to show notifications, in order to do so I need to trigger my custom notification function within the reducer so:
case REDUCER_ACTION_NAME:
notificationDisplay.success("Message", {
additionalStuff: extraOptions
});
What's even worse I'm using react-intl for translations and I need the "Message"o be translate-ready so I'm adding this to the mix:
case REDUCER_ACTION_NAME:
notificationDisplay.success(<FormattedMessage id="message" defaultMessage="Message" />, {
additionalStuff: extraOptions
});
It creates a translation in span and requires react so my reducer starts with these imports:
import React from 'react';
import notificationDisplay from 'my-notifications';
import { FormattedMessage } from 'react-intl';
import {
// all the actions
} from './actions.jsx';
// reducer
Is it okay? I feel something here is off - like importing React in reducers is an anti-pattern, because all reducer examples I could find are so clean and sleek and there are no external libs there whatsoever.
Am I right or am I wrong and my code is perfectly fine?
You should not do any kind of computations in reducer. It should change the state and nothing else. The way you are using it is a complete anti-pattern. Because it is doing some UI actions. And Redux is nothing to do with the UI. It should be used as the store and only the store.
But you can use Actions which is way better than doing it in reducer.
Best way to achieve your goal is to use your reducer to just push the messages into an array in the redux store. And create a Container that uses that messages array to show success or error messages. And create a timer that removes the message from the array after some time.
Just look at the https://github.com/diegoddox/react-redux-toastr repo they are doing it very well.
Thanks
Akhil P

how to reduce redux boilerplate

I'm new to redux and I find that every little thing x turns into x_success and x_failure, usually when fetching data or trying to create a new entity, and that means more action creators, and more handling in the reducers. What's the recommended approach here? thanks.
Recommended approach is x_success, x_failure etc. But this is for async operations only. Let's see why :
Async operations in your SPA are the operations you want to know
when operation started,
when you got response back
Type of response , success or failure
So that you will have seperate actions creator functions which return objects and one async action creator function which can return function instead of object and calls other action creators from its body.
For the reasons above you should have seperate action creators, one async action creator and of course for every action creator you should have a constant in your reducer.
Assuming you are writing your constants, actions and reducers in seperate folders, this can be a nightmare. If this is the case, you should take a look at here duck modular redux .
Duck modular redux is something you should definetely implement to reduce the boilerplate. Other things like seperate action creators, seperate constants, seperately checking on reducers for constants are required. There is nothing to feel bad about it.
I started to work with Redux almost since the beginning of its story, 2 years ago. While it is a great thing and it allows to eliminate a whole type of bugs and makes all business logic more explicit, it has a lot of concepts. For each entity, you need to create:
constants (for each state -- in case of async function, there are three of them, for start, failure and success)
action (function)
reducer (update state, sometimes a nested update)
It was mentioned that you can something like redux-ducks, which is an approach to organize your code, but you will not write less of the code. So, I strongly believe people should consider writing their own wrapper around redux more seriously.
I wrote a library redux-tiles, which deals exactly with this kind of situations -- it takes the burden of creating constants, update the state (so you don't need to write a reducer by yourself), and do nested updates if needed. So, code for async request, will look something like this:
import { createTile } from 'redux-tiles';
const apiRequest = createTile({
type: ['api', 'request'],
fn: ({ api, params }) => api.get('/api/items', params),
});
It also allows you to combine other actions easier, because in the fn you have access to dispatch and actions. You can take a look at more examples here.
I started using Redux and I loved the concept, but I got really annoyed by the big amount of boilerplate. I ended up creating actionware lib. Basically here's what you have with actionware:
no more action creators and action types, just actions (simple functions) and reducers
actions dispatch their result automatically
error status for every
action with no extra code busy status for every async action (yep, no extra code!)
cancellable actions
There are a number of ways you can reduce the bolierplate. Here's an excellent resource from official repo:
https://github.com/reactjs/redux/blob/master/docs/recipes/ReducingBoilerplate.md
Besides, there are various community-driven libraries that help you in reducing redux boilerplate and in organising your store better. Redux Box might interest you in this regard:
https://github.com/anish000kumar/redux-box
A huge Boilerplate is one of the drawbacks of the flux architecture in general. You might want to checkout redux-fluent, it has been designed with that in mind.
import { createAction, createReducer, ofType } from 'redux-fluent';
const addTodo = createAction('todos | add');
export const todos = createReducer('todos')
.actions(
ofType(actions.addTodo).map(
(state, { payload }) => state.concat(payload),
),
)
.default(() => []);
To reduce Redux boilerplate you can generate action creators and reducer by describing one function with redux-blaze:
import { buildReducer } from "redux-blaze";
export const { actionCreators, reducer: filtersReducer } = buildReducer(initialState, {
setMySearch: ({ search }) /* <- payload */ => state => ({ ...state, search }),
setCategory: ({ category }) => state => ({ ...state, category }),
setSort: ({ sort }) => state => ({ ...state, sort }),
}, {
prefix: 'MY_FILTER',
});
...
// Just add auto generated reducer to root reducer:
export const rootReducer = combineReducers({
filters: filtersReducer
})
...
// dispatch an action:
dispatch(actionCreators.setCategory({category: 'my category'}))
You can also find TypeScript examples here:
https://github.com/spiderpoul/redux-blaze

Resources