redux reducer vs action - redux

Newbie question regarding reducer vs action. From redux documentation:
Actions describe the fact that something happened, but don’t specify
how the application’s state changes in response.
and
Given the same arguments, reducer should calculate the next state and
return it. No surprises. No side effects. No API calls. No mutations.
Just a calculation.
So if we consider the following scenario:
User can place points on a map and get route between those points.
When user first clicks on a map this is his starting point. When he clicks for the second time - this is his ending point. Consequent clicks add points between the previous point and end location.
After each point is added (except for the first one) route must be calculated between new point and previous point. So if i have S -> A -> F and add point B (S -> A -> B -> F) two routes must be calculated A -> B and B -> F
So we kind of have two side effects upon adding any 3+ point:
New point is placed not at the end of the list
New route must be calculated to the Finish point.
If i model my Point structure as this:
// typescript
interface Point {
coordinates;
routeTo?;
}
Am I correct to perform item position calculation and route retrieval in Actions, eg:
// pseudo code
export function addMapPoint(newPoint) {
return (dispatch, getState) => {
const {points} = getState();
const position = getInsertPosition(points, newPoint)
dispatch(addPoint(newPoint, position));
if (points.length >= 2) {
const previousPoint = getPreviousPoint(points, position);
calculateRoute(newPoint, previousPoint).then(route => {
dispatch(updateRoute(newPoint, route))
})
}
}
}
To me this somehow contradicts to the "but don’t specify how the application’s state changes" - because from action i'm specifying where to insert my new point.
I could calculate the insert position in reducer, but then how do i fetch route data for the Finish point?
What is the correct approach here?

Assuming we have a calculateRoute function that accepts two points, and returns a promise that resolved the route between them.
First, let's create a simple action creator so we know our points are stored correctly:
let addPoint = (point, index) => {
return {
type: 'ADD_POINT',
point: point,
index: index
}
}
Then, let's handle this action in the reducer:
let reducer = (state = { points: [] }, action) => {
switch (action.type) {
case 'ADD_POINT':
return Object.assign({}, state, {
points: [
...state.points.slide(0, action.index),
action.point,
...state.points.slide(action.index + 1)
]
});
default:
return state;
}
}
Now, after the users add a point, we create an action using addPoint and dispatch it, so far so good, but this is the easy stuff.
The structure I strive for is to have a routes list in my reducer too, so let's extend it to support that:
let reducer = (state = { points: [], routes: [] }, action) => {
switch (action.type) {
case 'ADD_POINT':
return Object.assign({}, state, {
points: [
...state.points.slide(0, action.index),
action.point,
...state.points.slide(action.index + 1)
]
});
case 'UPDATE_ROUTES':
return Object.assign({}, state, {
routes: action.routes
});
default:
return state;
}
}
And the action creator will be:
let updateRoutes = (routes) => {
return {
type: 'UPDATE_ROUTES',
routes: routes
}
}
Please notice we're overriding the entire routes collection. For now it's OK, but probably in a production system you would want to optimize it a little bit.
Now we actually need to write some logic. I will assume a convenient assumption that we have a calculateRoutes that gets a collection of points, and returns a promise that resolves a list of respective routes, each route will be an object, containing two points and the actual route. Having said that, our thunk will now look like this:
addPointAndUpdateRoutes = (point, index) => {
return (dispatch, getState) => {
// First, update the point list
dispatch(addPoint(point, index));
// Now, recalculate routes
calculateRoutes(getState().points)
.then(routes => dispatch(updateRoutes(routes));
};
};
Which is way nicer in my opinion.
Now, off course that assuming we have a magical calculateRoutes function is not a serious assumption, though it's not a super hard task to implement this function in an optimized manner (meaning actually send the server only routes that we did not calculate before, etc). BUT this is just logic and NOT the state of the application, thus, as long as you keep the "contract" defined by the store and reducers, you are free to implement it any way you'd like.
Hope this helps you.

Related

How to correctly return array in redux state, if the array did not have to be updated in the reducer?

I am using the aurelia-store state management library for managing state. This question is not specific to Aurelia store, but actually to redux best practices in general since Aurelia store is very much the same thing.
I have an action that fetches unit updates from an API like so:
export const fetchNewUnits = async (state: State): Promise<State> => {
const fetchedUnits = await apiClient.getUnitsMarkers();
// no new updates so don't trigger change in units
// IS THIS ACCEPTABLE?
if (fetchedUnits.length === 0) {
return {
...state,
highwaterMark: new Date()
};
}
const units: UnitMarker[] = state.units.slice();
_.forEach(fetchedUnits, (newUnit) => {
// look for matching unit in store
const idx = _.findIndex(units, {
imei: newUnit.imei
});
// unit was found in store, do update
if (idx !== -1) {
// replace the unit in the store
const replacement = new UnitMarker({...newUnit});
units.splice(idx, 1, replacement);
}
});
// OR SHOULD I ALWAYS DEEP COPY THE ARRAY REFERENCE AND IT'S OBJECTS
return {
...state,
highwaterMark: new Date(),
units: [...units]
};
};
If I do not have any unit changes (i.e. my store is up to date) can I simply return the state with the spread operator as shown in the first return statement? Is this fine since I did not modify the objects?
Or do I always have to do deep replacements such as:
return {
...state,
highwaterMark: new Date(),
units: [...state.units]
};
even if the objects in the array did not change?
The reason why you’re supposed to create a new object is because React components check for prop changes in order to know when to re-render.
If you simply modify an object and pass it in as a prop again, React won’t know that something changed and will fail to rerender.
So in your case, the question is: do you want to rerender, or not? If you don’t, returning the same object is fine and a simple ‘return state’ will let React know that no rerenders are necessary.
See: Why is the requirement to always return new object with new internal references

Redux will execute all subscription callbacks every time an action is dispatched?

Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.

Redux - Is there any way to access store tree in reducer?

In my case, I have a store like:
{
aa: {...},
bb: cc // the result of computing with aa
}
I need to update aa and bb at the same time, but bb need to get the latest computation of aa.
Here is some code(React.js):
onClick(e) {
const { dispatch, aa, bb } = this.props;
dispatch(updateAa());
dispatch(updateBb(aa)); // can not get the latest computation of aa, it is the last computation..
}
So, is this mean that I need to get aa in bb's reducer?
And How can I do it?
Hope for helps!, Thanks!
don't use combineReducers.
Example
replace this code
export const a = combineReducers({
app,
posts,
intl,
products,
pos,
cats,
});
with
export default (state = {}, action) => {
return {
app: app(state.app, action, state),
posts: posts(state.posts, action, state),
intl: intl(state.intl, action, state),
products: products(state.products, action, state),
pos: pos(state.pos, action, state),
cats: cats(state.cats, action, state),
};
};
reducer would be like
const reducer = (state = initialState, action, root) => {....}
There are several possibilities, but it's tough to say which is best, given the vagueness of the code.
Ideally, your store should be normalized, meaning that each piece of data is only available in one place. Then you would calculate derived data after reading the store, such as when you use the selector pattern described in the guide to map the state of the store to what you might consider a materialized view that will be sent to your components as props. In this workflow, aa and bb would each be produced by selector functions, rather than stored in that store itself.
You could leave the reducer that updates aa and bb outside of combineReducers, so that it sees the whole state, rather than the state scoped down to aa and bb.
You could factor out your calculation code into a helper that could be called by updateAa and updateBb, and pass enough info in each action to make the calculation.
You could calculate the update before dispatching, so that the action contains the right value.
As David L. Walsh said, probably you should structure your reducers in a more logical way.
BUT If you still think you need it, you can use a thunk Middleware.
(https://github.com/gaearon/redux-thunk)
Redux Thunk middleware allows you to write action creators that return a function instead of an action.
Redux Thunk offers you a way to read the current state of the Redux store. In addition to dispatch, it also passes getState as the second argument to the function you return from your thunk action creator.
export function action() {
return function(dispatch, getState){
const state = getState()
dispatch({
type: "ACTION_WITH_SOME_PART_OF_STATE,
some_part_of_state: state.some_part
})
}
}
Ask yourself whether you've structured your reducers correctly. If a and b are not independent of one another, why are they separate reducers? I would try to merge them into a single reducer.
Based on Sheikh Abdul Wahid's answer, I had to do the following modification to make it work with history and connected-react-router:
Notice the () after the connectRouter(history)
import { connectRouter } from 'connected-react-router'
const createRootReducer = (history) => {
return (state = {}, action) => {
return {
...reducers,
router: connectRouter(history)(),
...rest of reducers
}
}
}
If this is a common use case for you, you can try writing your own function to combine reducers according to your needs, as recommended by the official Redux documentation:
Sharing data between slice reducers
Similarly, if sliceReducerA happens to need some data from sliceReducerB's slice of state in order to handle a particular action, or sliceReducerB happens to need the entire state as an argument, combineReducers does not handle that itself. This could be resolved by writing a custom function that knows to pass the needed data as an additional argument in those specific cases, such as:
function combinedReducer(state, action) {
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
I highly recommend you to read this documentation page, where there are also other suggestions to share data between reducers, even using combineReducers for simple actions and other custom reducers for the special cases.
I hope these options help!
You can access the other reducer's data in actions and dispatch that data as a param.
actions.js
const actionFn = (param1) => {
return (dispatch, stateFn) => {
const { param2 } = stateFn().other.reducer;
dispatch({
type: ACTION,
param1,
param2,
});
};
};
reducer.js
case ACTION:
return reducerFn(state, data);
const reducerFn = (state, { param1, param2 }) => {
return {
...state,
someState: {
...state.riverhealth,
setParam1: param1
setParam2: param2,
},
};
};
Hope it helps!
If some reducer needs some data from another reducer, a simple solution is to merge them into a single reducer.
In my case, I need some data from another reducer and it is very difficult to manage them so I ended up merging them both.

Should actions like showing/hiding loading screens be handled by reducers to related actions or generated by action creators themselves?

Given that you have some global view (showing a loading screen, for example) that you may want to happen in many cases, is it more appropriate to create an action creator/action pair for that behavior or to have the reducer for the related actions handle the transition?
This is hard to describe succinctly, to illustrate what I mean, here's a couple of examples. Which is better, and why?
A
function showLoading () {
return 'SHOW_LOADING';
}
function hideLoading () {
return 'HIDE_LOADING';
}
function fetchPostsRequest () {
return 'FETCH_POSTS_REQUEST';
}
function fetchPostsSuccess () {
return 'FETCH_POSTS_SUCCESS';
}
function doSomethingAsync () {
return dispatch => {
dispatch(showLoading());
dispatch(fetchPostsRequest());
// other logic
dispatch(hideLoading())
dispatch(fetchPostsSuccess());
}
}
function rootReducer (state = {}, action) {
const payload = action.payload;
switch(action) {
case 'SHOW_LOADING':
Object.assign({}, state, {isLoading: true})
break;
case 'HIDE_LOADING':
Object.assign({}, state, {isLoading: false})
break;
// other reducers for handling success/request stuff
}
}
B
function fetchPostsRequest () {
return 'FETCH_POSTS_REQUEST';
}
function fetchPostsSuccess () {
return 'FETCH_POSTS_SUCCESS';
}
function fetchPostsFailure () {
return 'FETCH_POSTS_FAILURE';
}
function doSomethingAsync () {
return dispatch => {
dispatch(fetchPostsRequest());
// good
dispatch(fetchPostsSuccess());
// bad
dispatch(fetchPostsFailure());
}
}
function rootReducer (state = {}, action) {
const payload = action.payload;
switch(action) {
case 'FETCH_POSTS_REQUEST':
Object.assign({}, state, {isLoading: true})
break;
case 'FETCH_POSTS_SUCCESS':
Object.assign({}, state, {isLoading: false /* other logic */})
break;
case 'FETCH_POSTS_FAILURE':
Object.assign({}, state, {isLoading: false /* other logic */})
break;
}
}
I prefer A because it seems more sensible to me to describe these behaviors in one place rather than duplicate the logic of state management, but I have heard a maxim in the redux community that actions should describe what happened or is happening, rather than be imperative commands. In which case, is this just a semantic issue where a term like "ASYNC_OPERATION_START" is better than "SHOW_LOADING"?
Think how this particular piece of code will evolve.
Use this to make decisions.
For example, you are likely to have more than one set of items that can be loading. You may eventually have two lists of items side by side, or one below the other. Thus you will want them to have separate isLoading state just like they have separate lists of IDs.
How would the code change in both options? It seems that having less actions is simpler because this lets you keep isLoading state of the particular list close to other information about it, and also not worry that you forgot to reset its state in the action creator. So in this case I'd choose option B.
On the other hand, if we're talking about a use case like showing a UI notification, I'd probably fire that as a separate action. It exists fairly independently of the server response that caused it: the notification needs to be hidden after a while, two notifications may live on the screen at the same time, etc. So, with this use case, option A seems a better fit.
In general, you should ask yourself:
How will this piece of code likely evolve?
Are these two actions really the same one or are they just related but independent?

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources