Is a redux-saga an efficient replacement for a middleware? - redux

I'm writing some exception logging code using the raven-js lib, and I'd like to log every redux action to create a breadcrumb trail of pre-exception activity.
I know this is a use case for middleware, but I'd find it much easier/more flexible to write a simple saga:
function* logReduxAction(action) {
// promisified Raven.captureBreadcrumb()
yield logBreadcrumb({ 'redux',action.type })
}
export const exceptionSagas = [
fork(takeEvery, '*', logReduxAction),
]
Is this an anti-pattern? Is capturing an action via saga notably less efficient than middleware? Or some other 'gotcha'?

Well redux-saga is a middleware. What you are asking is should you use ready-made middleware for logging events or come up with your own solution using sagas. I would say doesn't matter that much, use a solution which better suits you.

Related

Redux CreateAsyncThunk fetching data

I'm currently exploring the new Redux Toolkit setup. However, I stumbled upon CreateAsyncThunk when fetching data, but I cannot find pros and cons using this over a normal service class using e.g. axios.
Can someone explain to me why I should/shouldn't use the CreateAsyncThunk to fetch data from an api?
Redux is the place where you store your data.
So after your request you need to dispatch an action, to get your data into Redux.
Now, often your components are also interested in the question if you already are running a request.
That information could be stored in your component, but then other components won't know. So you store it in Redux, too.
Now you need to also dispatch an action to signal Redux that you started loading data.
The same goes for errors. You need to dispatch an action to signal Redux that loading failed.
createAsyncThunk takes code from you and executes that. But before, it dispatches an action. And depending if your code was a succcess or had an exception, it also dispatches either a "fulfilled" or "rejected" action for you.
So you only have to concentrate on the code you want to write.
So nothing stops you from doing
const myThunk = createAsyncThunk('myThunk', myApiService.getSomeData)
in the end - which would be using your api service, coupled with createAsyncThunk for the lifecycle actions - assuming your api service only takes one argument, otherwise it'd be
const myThunk = createAsyncThunk('myThunk', ({name, password}) => myApiService.getSomeData(name, password))
since cAT only forwards one argument from the action creator call.

Redux Saga async/await pattern

I'm using async/await throughout my codebase. Because of this my api calls are defined by async functions
async function apiFetchFoo {
return await apiCall(...);
}
I would like to call this function from my saga code. It seems like I can not do this:
// Doesn't work
function* fetchFoo(action) {
const results = await apiFetchFoo();
yield put({type: "FOOS_FETCHED_SUCCESSFULLY", foos: results});
}
However, this does work, and matches the redux saga documentation:
// Does work
function* fetchFoo(action) {
const results = yield call(apiFetchFoo);
yield put({type: "FOOS_FETCHED_SUCCESSFULLY", foos: results});
}
Is this the correct way to use Redux Saga alongside async/await? It is standard to use this generator syntax inside of the saga code, and the async/await pattern elsewhere?
Yes, that's the standard way to use Redux-Saga.
You should never be calling the await function directly inside the saga-generator, because redux-saga is for orchestrating the side-effects. Therefore, any time that you want to run a side-effect you should do it by yielding the side-effect through a redux-saga effect (usually: call or fork). If you do it directly without yielding it through a redux-saga effect, then redux-saga won't be able to orchestrate the side-effect.
If you think about it, the redux-saga generator is completely testable without the need of mocking anything. Also, it helps to keep things decoupled: if your apiFetchFoo returned a promise, the saga would still work the same.
As pointed out by Josep, await cannot be used inside a generator. Instead you need to use an async function. Also, note this is a limitation of async function itself. It is not imposed by redux-saga.
Beyond this, I also wanted to mention that it is a conscious choice by the redux-saga authors to not allow devs to express sagas as async/await functions.
Generators are more powerful than async/await and they allow advanced features of redux-saga like co-ordinating parallel tasks.
Moreover, expressing sagas as generators help us define Effects which are plain objects defining the side effect. Effects makes it very easy to test our sagas.
So, although your working code is fine, maybe not mixing up sagas and async function is a good idea.
Just define your apiFetchFoo to return a promise which resolves with the response to the request. And when this happens your saga will resume with the results.
const apiFetchFoo = () =>
fetch('foo')
.then(res => res.json())
As the previous answers says , Redux saga uses side effects which handles the async within it so the way of doing it , Is using yield and call if calling an API and so on so far
await always work within a function that's declared as async. #thumbRule
async function fetchList () {
let resp = await fetchApi([params]);
}

Redux - Use action object method in reducer instead of switch

I'm new to redux and looked at redux-actions or using switch statements in reducer, and though I'm not against using a switch statement, I'm wondering, isn't it easier to just use the call the action method?
Here's what I'm thinking
import actions from './actions'
const reducer = (state = {}, action) => {
if (actions[action.type]) return Object.assign({},
state, actions[action.type](action)
);
return state;
}
I've just tested this on my first reducer and action, and it works, but it seems quite obvious so I'm wondering why the switch type is the chosen way?
Switch statements are certainly the most common approach, but lookup tables are common as well. You can even use plain if/then conditions if you want. Ultimately, how you write your reducers is up to you.
FYI, this topic is covered in the Redux FAQ, in the FAQ: Reducers section. You might also want to read the new "Structuring Reducers" how-to section as well.
Some observations:
Don't refer to these external functions as "actions". They're not actions. They're actually reducers themselves.
Being reducers, you really ought to be passing the state object to them. Oftentimes, you'll want/need to utilise information contained in the current state, as well as information contained in the action object.
Otherwise, this seems like an appropriate approach.

How to have multiple reducers trigger updates based on a common set of actions without repeating yourself?

I would like many different redux actions in my app to all trigger common functionality in a specific reducer. I would like to avoid having to either repeat some flag in every action creator (like doThing: true) that the reducer looks for. I also don't want to have to have the reducer just look for every individual action that falls into this category, since that also requires someone to remember to do this every time they add a new action, like adding the flag.
I was thinking of dispatching a second action every time one of these actions is going to be dispatched. This would not be hard to do, but I'd rather not have 2 actions dispatched every time one thing happens. It seems like it would pollute the state history.
Is there a common way of solving this problem?
For more context to my specific problem, the specific feature is related to the API client my app uses to talk to our API. On every successful response, we'd like to do something in a reducer to update the state, and on every failed response, we'd like to do something else.
There are many different success and failure actions (such as ITEM_FETCH_SUCCESS or WIDGET_UPDATE_FAILURE), and adding a flag to all of them would be hard to remember to do when new ones are added.
Since all api requests go through a single function, that function COULD dispatch generic REQUEST_SUCCESS and REQUEST_FAILURE actions. But this would mean every response from the server would dispatch 2 actions (REQUEST_SUCCESS and ITEM_FETCH_SUCCESS). This is obviously not ideal since it would mean many more actions in my state history.
Assuming the generic REQUEST_SUCCESS and REQUEST_FAILURE actions are updating their own specific portions of the state-tree then it is fine to dispatch them as distinct actions. Doing this does not necessarily imply the pollution of your state history but can simply be a better description of the app's intentions.
ITEM_FETCH_SUCCESS: Change state for item
REQUEST_SUCCESS: Change state for request
WIDGET_UPDATE_FAILURE: Change state for widget
REQUEST_FAILURE: Change state for request
You can see that whilst the actions are intimately related, they are not necessarily the same thing as they change different parts of the state tree.
Accepting this, the question is: How best to implement the action-pairs so that adding new actions does not mean remembering to add its corresponding REQUEST_* partner?
I would consider applying a simple redux middleware component. This could intercept the return from your api and dispatch the appropriate REQUEST_* action automatically.
Here is an example from some live code. This middleware intercepts a disconnect event raised by a websocket and automatically dispatches a custom action as a result. It at least shows the principle:
//Dispatch a disconnect action when the websocket disconnects
//This is the custom action provided by the middleware
import io from 'socket.io-client'
import { actions } from './action'
const websocket = ({ websocketUrl }) => store => {
const socket = io(websocketUrl)
socket.on('disconnect', () => store.dispatch(actions.disconnect()))
}
export default websocket
//Apply the custom middleware via the redux createStore function
//Also include the thunk middleware because it is useful
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import websocket from './middleware'
function websocketize (opts) {
return createStore => (reducers, initial, enhancer) => {
const middleware = applyMiddleware(thunk, websocket(opts))
return createStore(reducers, initial, middleware)
}
}
export default websocketize
// Create the top-level redux store passing in the custom middleware enhancer
const opts = {websocketUrl: env.WEBSOCKET_URL}
const store = createStore(reducers, websocketize(opts))
This implementation keeps everything inside your reducers as opposed to having logic outside in an interception(middleware). Both ways are valid.
Try a sub-reducer pattern. I usually feel gross when I see it used(because it is usually used wrong), but your situation sounds perfect.
Extract duplicate functionality out of your reducers to one single
sub-reducer.
Then pass that reducer as a function to all others that need it.
Then pass the action and state onto the sub-reducer.
The sub-reducer does it's thing and returns that slice of state to
your parent reducer to allow you to do whatever you want with it
there (ie return it, mutate some more, some logic).
Also if you are tired of worrying about typing out "all the stuff" for async then I highly recommend you try out redux-crud.js
It also is possible and a simple way to do that would be to give every action to one reducer and let it do that common mutation, in a single case:
case actionOne
actionTwo
actionThree
actionFour: {
//do common stuff here
}
. But you said it is not duplicated, it is similar, which means your case becomes complicated by branching logic. I also don't recommend this. Keep cases simple so you can easily catch invalid mutations. This should be a super power of redux that it is easy to catch mutation errors. And for this reason and many others I don't recommend normalizing data in the front end.

Dynamic middleware in Redux

I'm using Redux to write a NodeJS app. I'm interested in allowing users to dynamically load middleware by specifying it at runtime.
How do I dynamically update the middleware of a running Redux application to add or remove middleware?
Middleware is not some separate extension, it's part of what your store is. Swapping it at runtime could lead to inconsistencies. How do you reason about your actions if you don't know what middleware they'll be run through? (Keep in mind that middlewares don't have to operate synchronously.)
You could try a naive implementation like the following:
const middlewares = [];
const middlewareMiddleware = store => next => act => {
const nextMiddleware = remaining => action => remaining.length
? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action)
: next(action);
nextMiddleware(middlewares)(act);
};
// ... now add/remove middlewares to/from the array at runtime as you wish
but take note of the middleware contract, particularly the next argument. Each middleware receives a "pass to the next middleware" function as part of its construction. Even if you apply middlewares dynamically, you still need to tell them how to pass their result to the next middleware in line. Now you're faced with a loss-loss choice:
action will go through all of the middleware registered at the time it was dispatched (as shown above), even if it was removed or other middleware was added in the meantime, or
each time the action is passed on, it goes to the next currently registered middleware (implementation is a trivial excercise), so it's possible for an action to go through a combination of middlewares that were never registered together at a single point in time.
It might be a good idea to avoid these problems alltogether by sticking to static middleware.
Use redux-dynamic-middlewares
https://github.com/pofigizm/redux-dynamic-middlewares
Attempting to change middleware on-the-fly would violate the principle of 'pure' actions and reducer functions, because it introduces side-effects. The resulting app will be difficult to unit-test.
Off the top of my head, it might be possible to create multiple stores (one for each possible middleware configuration), and use a parent store to provide the state switch between them. You'd move the data between the sub-stores when switching. Caveat: I've not seen this done, and there might be good reasons for not doing it.

Resources