I've just started to learn and use redux-saga, at first I thought it worked this way: first you make action which is connected to saga, then saga detects action,calls api which returns data and saga returns this data to reducer. I built 2 testing saga files and one root saga (I wanted to make rootSaga, like combineReducer), so this is my rootSaga :
import * as SpreadsheetSagas from "../pages/spreadsheet/containers/spreadsheet/spreadsheet.saga";
import * as SpreadsheetFilterSagas from "../pages/spreadsheet/containers/spreadsheet_filter/spreadsheet_filter.saga";
export default function* rootSaga() {
yield all([
...Object.values(SpreadsheetSagas),
...Object.values(SpreadsheetFilterSagas)
].map(fork))
}
and this is one of my saga function:
export function* getData() {
const response = yield call(ApiService.get);
const payload = response ? response.data : {};
//send returned object back to reducer as payload:
yield put({ type: 'GET_MOCK_DATA', payload});
}
and my store folder looks like:
const middleWare = [];
// Setup Redux-Saga.
const sagaMiddleware = createSagaMiddleware();
middleWare.push(sagaMiddleware);
const store = createStore(rootReducer, {},
compose(applyMiddleware(...middleWare)));
// Initiate the root saga.
sagaMiddleware.run(rootSaga);
So, when I run my app, it calls every saga functions - and this is correct way to implement sagas :? or I should have several rootSaga files which will be depend on current page, and then when I open page, run appropriate rootSaga? I hope I explained everything correctly :/ please give me any suggestions. thank you.
I think generally you just have one tree of sagas that relate to your entire app, not multiple root sagas for different pages/routes. You just attach the rootSaga once, as middleware.
So yes, when your app starts the entire tree of sagas starts and beings watching actions. Sagas interact globally with your store, so it makes sense for them to start globally with your app.
Only start the sagas that watch for actions (using take, takeEvery etc). Other sagas that make side effects, or call apis, shouldn't be invoked when your app starts.
Creating a tree of sagas similar to reducers and combineReducer is the right way to go too.
Related
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.
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 :-)
Currently, I have 20+ routes in my application. Each of these routes will have multiple API calls, so I plan to use redux-saga to make AJAX calls.
From reading the documentation, it looks like I'll be able to do the following:
Each route will have it's own "root" saga, yielding multiple sagas itself.
This route specific saga will then be exported to the actual main root saga, which in turn will be passed to createSagaMiddleware.run().
So it will look something like this:
export function* screen1Saga() {
yield [ ... ]; // different sub-sagas for screen1 route
}
Then in the main saga file, do this:
import { screen1Saga } from './screen1/sagas';
export function* rootSaga() {
yield [ screen1Saga(), ... ]; // array of route-specific sagas
}
I believe this should work perfectly fine. But I'm worried about how it will look when 20+ route specific sagas are created. In the root saga, it will just be a huge array of sagas.
Is it possible to do this some other way? Preferably in such a way that route specific sagas are encapsulated within it's corresponding folder without the need to export it all the way to the top of the app structure?
Just curious to see how others have dealt with a large number of sagas.
The React-Boilerplate repo is a pretty good example of some ways to scale a React app. They use a utility file called asyncInjectors, which really just delegates the work of adding sagas by calling sagaMiddleware.run(saga) underneath the hood.
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:
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