Adding a callback to a Redux Reducer - reactjs

Would there be anything wrong/anti-pattern-ish (in terms of 'thinking-in-react/redux') in added a callback to the action.data passed into an action?
// reducer
ACTION_FOR_REDUCER() {
var x = 123
if ( action.data.callback ) action.data.callback( x )
return {
something: action.data.somedata
}
},
Then accessing that data later in the App when the action is called (in an container perhaps)
// later in the app
this.props.dispatch(changeSomething({
somedata: somedata,
callback: (x) => { console.log(x) }
}))

The idea is not wrong, the implementation is.
Instead of adding a callback, return a promise from the action (you will need redux-thunk middleware or some similar alternative).
Then you can simply do:
dispatch(myAction).then(callback);
Of course, you can also simply dispatch another action in your callback, which usually end up being one compound action.
const myAction = ...
const callbackAction = ...
const compoundAction = () => dispatch => {
dispatch(myAction())
.then(() => dispatch(callbackAction()));
};

The third principle of Redux (listed in the introduction section of the docs) is that 'changes are made with pure functions'.
A pure function is a function that always returns the same result given the same inputs, and doesn't cause any side effects. Having a callback log something out most definitely would be a side effect!
The docs go on to say:
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
The reasoning behind this is it makes your reducers predictable - you know that, given an action with a certain payload, you're guaranteed to have the same output. This makes your app both easier to understand as a developer, and easier to unit test.
If you want to perform side effects when an action takes place, you should install a middleware that allows you to do so - some common examples of these are:
redux-thunk, which allows you to dispatch functions that can in turn dispatch actions (commonly used to make API calls)
redux-logger, which allows you to log out actions that are dispatched.
redux-devtools-extension, which allows you to view your state and actions in a Chrome devtools extension.
If you can't find a middleware package that does what you need (rare, to be honest - there's lots out there already!), you can write your own quite easily.

Callbacks introduce side-effects, so when using Redux you should consider returning a new state with the result, for example with done: true flag and react to that in componentDidUpdate.
For managing asynchrony and effects you can use redux-saga, redux-thunk, RxJs or another library.
If you want to stick with callbacks, why even bother with Redux?

Related

Await value returned from a redux-thunk dispatch rather than using state

I am using react, redux, and redux-thunk have a set of queries I have to run in the "logic layer". The results of each "action" call determine the path I take, so I am actually awaiting the promises returned from the dispatch call to the redux-thunks.
const handleClick = async (args) => {
let foo = await dispatch(fetchFoo(args));
if(foo) { do something... }
else {
let bar = await dispatch(fetchBar(args));
if(bar) { do another thing... }
else { or do another thing... }
}
};
Example thunk:
export const fetchFoo = args => async (dispatch) => {
let foo = await api.fetchFoo(args);
dispatch(fetchSuccess(foo));
// !!!!!!!!!!
return foo;
// !!!!!!!!!!
}
If I don't do this, it's pretty awkward to wait until a re-render (maybe) puts "foo" in the redux state prop, then wait again until a re-render (maybe) puts "bar" in the redux state, etc...
I've never really seen this pattern before although I have seen awaiting void promises return from thunks.
Is it acceptable to return the value from the redux-thunk action and use it rather than getting the values from a redux state selector? This seems to break the rules of the "single source of truth." If not, what do I do?
Using this approach it would be much harder to refactor state and change flow how data is loaded. Say in case requirements are changed and fetchBar() should be called by interval or need to be loaded by parent and passed through props. Or you want to throttle loading. Or cache data for some time.
I used this pattern with Promise returned and found it makes things rather complicated. The only advantage I found: I didn't need having isLoading or loadingError flags in redux storage to know if data is already loaded or not.
But also it means I have to call some methods redundantly just to retrieve brand new Promise await for - since promises can be resolved only once - in order to wait until some data loaded. And once some action triggers few API calls in sequence I ended with awaiting for data I did not even need.
In opposite if we rely only on data in redux it would be as easy as const isStillLoading = props.isData1Loading && props.isData2Loading
Consider moving condition for loading data into action itself. If you find API calls sequence becomes too complex you may switch to redux-saga or redux-loop which both provide better control over execution flow.

Get state and make dispatch from middleware

In my React application i'm using Long-polling API. In order to automatically send requests on every response i use middleware. But before sending new request, i have to save received data in store. More than that, i want to dispatch another action inside my middleware. So my structure looks like this:
InitLongPoll() -> SendRequest(data) -> ReceiveResponse(data)* -> SendRequest(data)
'*' is my middleware. From there i'm saving data to the store using store.dispatch(responseData) and sending new request using store.dispatch(sendRequest(authData)).
Is it okay to receive that authData using store.getState().authReducer? As far as i know, my middleware should be a pure function and shouldn't depend on external data (store). Thanks in advance.
Is it okay to receive that authData using
store.getState().authReducer? As far as i know, my middleware should
be a pure function and shouldn't depend on external data (store).
Yes it is. Middleware is the way to introduce side effects into the redux loop, and it can't be a pure function. Your own middleware has a side effect - polling the server.
A redux middleware is the anti-thesis of a pure function, which is defined as:
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any
hidden information or state that may change while program execution
proceeds or between different executions of the program, nor can it
depend on any external input from I/O devices (usually—see below).
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output
to I/O devices (usually—see below).
You can also see in the redux-thunk source code, that it uses getState:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}

Why use redux-thunk or redux-saga for fetches?

I keep reading that I should use redux-thunk or redux-saga to handle side effects.
Why not simply use action creators like that to dispatch multiple actions :
function loadProductActionCreator(dispatch) {
dispatch({
type: 'load_product',
})
fetch('api/product').then(
function (r) {
return r.json();
}
)
.then(function (res) {
dispatch({
type: 'loaded_product',
data: res
})
})
}
I tried that and it worked (complete code). So I guess there must be some inconvenients I'm not aware of.
You code is similar to what thunk does.
As per redux docs, actions should be pure. And they should always return same values for same input parameters. By using fetch you are allowing action to return not specific value, rather value from server and that mean action response may vary upon time.
That is called side effects. And it's something what shouldn't be in redux actions by default.
But why?
Yes, you can type it inside action like you have, in small apps it does not matter.
In larger application there are benefits of using redux-saga:
actions are predictable, they just return payload like
{
type: 'FETCH_POSTS',
params: {
category: 'programming'
}
}
and then you build middleware which will take actions with all data required to perform request to real API
Possible advantages:
Cleaner codebase (but may be overhead on smaller applications)
Separation of "dummy" actions with all required information to perform requests and actual API middleware
Request parameters are visible directly in redux dev tools
Possible to easily debounce, throttle fetches which may be really tricky with redux-thunk
Possible to easily combine actions (wait for another event/fetch, chain events)
Possible to stop running tasks
From personal experience, on one project (larger codebase) we have started with redux-thunk, but later we needed to integrate more advanced features, like throttle, and some dependencies between actions. So we rewrote everything to redux-saga and it worked well for us.
You are kind of replicating redux-thunk here. A pure redux action creator should return an action object to be dispatched and not dispatch an action itself (see redux doc on action creator).
To better understand why your technic is a replication of redux-thunk, look at this post from its author

What are the most important considerations when designing Redux actions?

If I have a situation: a component has 3 operations(list all info || search needed info || filter info), but all of these operations depend on same API, just different parameters.
I'd like to know what is the best approach to design Redux actions? To design 3 actions that are mapping the 3 operations? Or just 1 action because of just 1 API?
What are the most important considerations when designing Redux Actions?
Whether it should be one or two or three actions really depends on how your reducers are organized, and on how your UI is connected to your store.
You should think of API calls as side-effects of your actions, and not as the actions themselves. Your redux store holds a particular state of your UI: what filter is active ? What is the current search query ? and the like...
The most important consideration when designing actions is : what do they mean for the state of my UI? What impact do they have on it ? How do they relate to my store's state ?
If your concern is to mutualize API calls code, there are various patterns to handle that : use a simple helper function taking parameters, use something like redux-saga to trigger side-effects, etc.. But all in all, API calls shouldn't be relevant when designing actions.
There's nothing in the docs that says that actions should map to API endpoints. So you shouldn't have to think of how many API endpoints you have.
From http://redux.js.org/docs/basics/Actions.html:
Actions are payloads of information that send data from your
application to your store.
In your case though, if one action with payload can accomplish the result and mutate your state to the desired condition, I don't see a reason to use three (and thus have three reducers, etc).
The redux docs have a good section on how asynchronous actions/API usage should work within a redux app.
http://redux.js.org/docs/advanced/AsyncActions.html
I've followed this pattern, using the thunk middleware, and found it to work really well. The idea is to give your actions the ability to return a function. You will have:
A "main" function that will dispatch that the application is making an API call, make the API call, and handle the results.
Two pure action creators (functions that return an object with a type and any number of other properties) that will pass the information related to the API call to your reducers.
The actions:
/* Inside ./action.js */
/* This is the function you will dispatch from another component (say, a container component. This takes advantage of thunk middleware. */
export function fetchAllInfo() {
return function(dispatch) {
dispatch(requestAllInfo())
/* Import your API function, make sure it takes a callback as an argument */
API.fetchAllInfo((allinfo) => {
dispatch(receiveAllInfo(allinfo))
}
}
/* Pure action creators */
function requestAllInfo() {
return {
type: "REQUEST_ALL_INFO",
}
}
function receiveAllInfo(allinfo) {
return {
type: "RECEIVE_ALL_INFO",
allinfo,
}
}
The reducer:
/* Inside ./reducer.js */
export function inforeducer(state, action) {
switch(action.type) {
case "REQUEST_ALL_INFO": {
return Object.assign({}, state, {
/* This can be passed as prop so a component knows to show a loader */
isFetching: true,
})
case "RECEIVE_ALL_INFO": {
return Object.assign({}, state, {
isFetching: false,
allInfo: action.allInfo,
})
}
default:
return state
}
}
So with thunk middleware (or your own way of dealing with asynchronous dispatching) and a callback on each API call, you can use this method to work with API calls dispatching at both the beginning of the call and when you receive results.

Should I use one or several action types to represent this async action?

I'm building a front-end for a search system where almost all user actions need to trigger the same async action to re-fetch search results. For example, if a user enters a keyword, then we need to fetch /api/search?q=foo, and if they later select a category we fetch /api/search?q=foo&categoryId=bar. I originally had separate action types for FETCH_RESULTS, SELECT_CATEGORY, DESELECT_CATEGORY, etc. I created one asynchronous action creator for FETCH_RESULTS, but the others are synchronous. The more I think about it, they all end up needing to re-fetching the results from the backend and update the app state based on the response from the backend.
Would it make sense for me to use the single async action-creator for any change? Or would it be better to use async action creators for each distinct user action (selecting a keyword, category, or filter)?
I think the advantage of granular actions would be the events more accurately reflect what the user did (e.g. the user selected a category) vs having to peer into the payload to figure out what actually changed, but they are all pretty similar.
This is of course something only you can really answer based on what you know about the project. I don't think that there is any inherent advantage to having the actions be more granular, and if there aren't any, its not worth the extra effort. I would have a generic FILTER_CHANGED event and not worry about being able to see what specifically changed--presumably the action isn't going to be complicated, so I'm not going to be debugging the action a lot. As the filter state becomes more complicated and diverse, it might make more sense to break out the actions. By default though, I don't really see much value.
I fully agree with Nathan’s answer.
I just want to add that in order to tell whether actions A and B are really one or two actions, you need to ask yourself: “If I change how some reducers react to A, will I also need to change how they react to B?”
When the handlers change together in the reducer code, it’s likely they should be a single action. When their changes may not affect each other, or if many reducers handle just one of them but not the other, they should probably stay separate.
I agree with Dan Abramov: if the text and categories are highly coupled in your interface, just fire FETCH_RESULTS with the text and categories as action payload.
If the text input and categories selection widget do not share a close parent component, it is complicated to fire a FETCH_RESULTS which contains the text and categories (unless passing a lot of props down the tree...): you then need the action granularity.
One pattern that I have found helpful when such granularity is needed is the Saga / Process manager pattern. I've written a bit about it here: https://stackoverflow.com/a/33501899/82609
Basically, implementing this on redux would mean there's a very special kind of reducer that can trigger side-effects. This reducer is not pure, but do not have the purpose of triggering React renderings, but instead manage coordination of components.
Here's an example of how I would implement your usecase:
function triggerSearchWhenFilterChangesSaga(action,state,dispatch) {
var newState = searchFiltersReducer(action,state);
var filtersHaveChanged = (newState !== state);
if ( filtersHaveChanged ) {
triggerSearch(newFiltersState,dispatch)
}
return newState;
}
function searchFiltersReducer(action,state = {text: undefined,categories: []}) {
switch (action.type) {
case SEARCH_TEXT_CHANGED:
return Object.assign({}, state, {text: action.text});
break;
case CATEGORY_SELECTED:
return Object.assign({}, state, {categories: state.categories.concat(action.category) });
break;
case CATEGORY_UNSELECTED:
return Object.assign({}, state, {categories: _.without(state.categories,action.category) });
break;
}
return state;
}
Note if you use any time-traveling (record/replay/undo/redo/whatever) debugger, the saga should always be disabled when replaying actions because you don't want new actions to be dispatched during the replay.
EDIT: in Elm language (from which Redux is inspired) we can perform such effects by "reducing" the effects, and then applying them. See that signature: (state, action) -> (state, Effect)
There is also this long discussion on the subjet.
EDIT:
I did not know before but in Redux action creators can access state. So most problems a Saga is supposed to resolve can often be solved in the action creators (but it creates more unnecessary coupling to UI state):
function selectCategory(category) {
return (dispatch, getState) => {
dispatch({type: "CategorySelected",payload: category});
dispatch({type: "SearchTriggered",payload: getState().filters});
}
}

Resources