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

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.

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.

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.
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 :-)

Data modelling my store in Redux

Consider this:
I have an application that is going to end up being pretty large. It is a dashboard which will give you access to various utilities, one of which being a todo app.
If I was just going to build just a todo app, then my state object would look like so:
{ todos:[], completed:false, todoInput:''};
todoInput would be tied to a form field and and upon clicking add, it would alter the todos array and toggle the completed field. So my combineReducers() function would look like this.
combineReducers({todos,completed,todoInput});
This would make sense because all the state is relevant to the todo App because there is JUST a todo app.
Now because I am building a much more complicated application which also has a todo app, this is how my state would potentially look like:
{
otherState:'',evenMoreState:[]',evenMore:{},
todo:{ todos:[], completed:false, todoInput:''}
}
As you can see I have separated todos into a separate object now, so it is encapsulated and more organised. So I have 2 questions.
1) Is this a good idea? It seems like the logical move because my application will grow in size and I don't want all the pieces of state floating around as properties to the main state object. Have I gone about this correctly?
2) My combine reducers (as far as I know) cannot take a nested object. So it will now look like this.
combineReducers({ otherState,evenMoreState,evenMore,todo})
so now my reducer compositions will have to be done inside the reducer which handles the todo state. Is there a better/different way to do this?
Thanks
Yes, you're absolutely on the right track. It's also worth noting that you can use combineReducers multiple times, such as:
const rootReducer = combineReducers({
otherState : otherStateReducer,
todos : combineReducers({
todos : todosReducer,
completed : todosCompletedReducer,
todoInput : todoInputReducer
})
The overall todos section could be be defined separately, and imported and referenced in the top-level combineReducers call if desired.
});
You may want to read through the Redux docs section on "Structuring Reducers" for more information on ways to organize reducer logic, as well as the Redux FAQ on organizing nested state. In addition, the Redux Techniques and Redux Architecture sections of my React/Redux links list have links to a variety of articles about building real-world Redux applications.

CombineReducers and good structure in react-redux

I made calendar part of app, and also tried to make it can be reusable.
Then I also made another part using calendar.
So I used combineReducers function.
import calendar from '../../common/calendar/Reducer';
import insatester from './Reducer';
const reducerCombiner = combineReducers({
calendar,
insatester
});
export default reducerCombiner
Then, I used the combined reducer to make a store.
It seems to work fine first.
But the store and data was separated.
example)
store.calendar.data1 <- from calendar
store.insatester.data2 <- from insatester
Then I tried to get calendar event DB data from insatester, because I think reusable calendar doesn't do that.
But the problem is using stored db data from calendar app also need to access store.insatester, it doesn't make sense.
Question1. How can I combine reducers to make the store having same level?
example)
store.data1 <- from calendar
store.data2 <- from insatester
Question2. Is my structure having problems? How can I change it?
You need to write a reducer that can access the full state. As you've seen, combineReducers limits you to a single slice of the state. You can put your custom reducer in front to handle the actions that need data from both slices, and let the rest fall into the combineReducers part.
This page of the Redux docs explains: http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html#sharing-data-between-slice-reducers

Where to put API calls in React/Redux architecture?

I am trying to retrieve some data from an API and pass it into my application. Being new to React/Redux however, I am wondering where to make these calls from and how to pass it into my application? I have the standard folder structure (components, reducers, containers, etc.) but I'm not sure where to place my API calls now.
The easiest way to get started with this is to just add it into your actions, using a function called a thunk along with redux-thunk. All you need to do is add thunk to your store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Then create a function in your actions that calls the api:
export const getData() {
(dispatch) => {
return fetch('/api/data')
.then(response => response.json())
.then(json => dispatch(resolvedGetData(json)))
}
}
export const resolvedGetData(data) {
return {
type: 'RESOLVED_GET_DATA',
data
}
}
The "Teach a man to fish answer."
This depends on the type of call and the situation.
Generally for simple "gets" this can easily be done by placing them
into your action creators as Nader Dabit has shown.
There are many side effect management libraries which would opt for
you to place them in their blocks(redux-sagas, axios calls, redux-thunk)
I use redux-sagas for now. At least until we decide yay or nay on async/await which is possibly coming in a newer version of JS.
This may be the most important part!
Just be sure to take into account the general "conventions" that are used with your particular set of tools usually found in the documentation, and be sure to google "best practices" in the future for things like this. This will help others new to your project to get their bearings and just jump in without ramping up learning your new customized version.
Behavior such as AJAX calls are referred to as "side effects", and generally live in either your components, "thunk" action creators, or other similar Redux side effects addons such as "sagas".
Please see this answer in the Redux FAQ for more details:

Resources