Running an action creator in response to an action (without saga) - redux

Is there a library for redux that allows a function to be run in response to a specific action type and have that function's return value (which may be an action/thunk/promise) dispatched? I know that saga allows for this, but saga is a bit full featured for this basic requirement.
I understand that this would be fairly trivial to write myself, but if someone else has already done it then there's no need to reinvent the wheel.

I would write my own middleware like this:
export default ({ dispatch }) => (next) => (action) => {
if (action.type === 'MY_ACTION') {
const anotherAction = someOtherFunction();
dispatch(anotherAction); // can use next() if you want to skip all middleware before this one.
} else {
next(action);
}
}

Related

What does cps do in redux saga?

It's about 5 months that I work with redux-saga, it's a great and strong middleware library.
I know almost everything in redux-saga, but I couldn't still understand "cps".
Can anyone explain me (with an example) what can be done with "cps"?
I really appreciate if someone takes me out of this confusion.
cps effect is there to easily handle asynchronous functions that receive a nodejs style callback as last parameter.
const doSomething = (param1, param2, callback) => {
setTimeout(() => {
callback(null, 'done')
}, 1000)
}
function* saga() {
const result = yield cps(doSomething, 'foo', 'bar')
console.log(result) // 'done'
}
Documentation: https://redux-saga.js.org/docs/api/#cpsfn-args

Redux middleware not dispatching new actions

I'm creating a Redux middleware that listens for a specific action. If that action type matches what I'm looking for, I want to dispatch another action. The reason for this is because I have different components with some shared functionality, so I want to update actions to have similar types, but different payloads (term), like so:
const updateActions = store => next => action => {
console.log(action);
if (action.type === 'UNIQUE_ACTION_A') {
return next({ type: 'NEW_ACTION', term: 'new_action_a_test' });
} else if (action.type === 'UNIQUE_ACTION_B') {
return next({
type: 'NEW_ACTION',
term: 'new_action_b_test'
});
}
return next(action);
};
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk, updateActions))
);
The problem I'm having is that these actions are not dispatching. When I console.log all actions, they continue to run as normal, rather than dispatching the new actions. It's as if the call to dispatch these actions are just being ignored. What am I doing incorrectly here? Thanks!
There's a difference between next and dispatch inside middleware. dispatch sends an action to the very start of the dispatch chain, which will cause it to run through all the middleware in the pipeline. next sends it to the next middleware after this one, and eventually to the reducers. By calling next({type : "NEW_ACTION"}), you're sending it to the next middleware, and this middleware will never see "NEW_ACTION".
Also see the Redux FAQ entry on next vs dispatch in middleware.

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.

Am I using Redux correctly?

this is just a question,
I'd love to double check if I'm doing things right. I'm coming from ages of different frameworks, and I def want to avoid bad practices in the early stage.
I'm using this boilerplate: https://github.com/erikras/react-redux-universal-hot-example
and I'm writing in ES7.
I created:
- 1 reducer: earlyUser.js
- 1 container: landingPage.js
- 1 component: registrationForm.js
In the landingPage, I'm including the methods from reducer in this way:
import { saveEmail, savePreferences, checkEmailExists } from 'redux/modules/earlyUser';
and I declare some handles
handleSubmitEmail(data) {
this.props.saveEmail(data);
}
handleSubmitPreferences(data) {
//some data manipulation
this.props.savePreferences(data);
}
and in the JSX part I just pass to my component the handlers:
<registrationForm submitEmail= {::this.handleSubmit} etc... >
Now inside the component, I linked the form submission to this handler:
submitEmail() {
if (this.validateEmail(this.state.email)) {
this.props.submitEmailHandler(this.state);
}
}
Now my question is, where should I attach the .then and .catch of the promise returned ?
Ideally I'd like to do inside the component.js something like
this.props.submitEmailHandler(this.state).then( function emailRegisterCallback(){
// move to next step
}).catch( function errorHandler(error){
// there was an error
}
Is that correct?
Also, is there the right syntax to handle promises in ES7 ?
You normally handle the async aspects in your action creators:
See this code from the async redux example:
function fetchPosts(reddit) {
return dispatch => {
dispatch(requestPosts(reddit));
return fetch(`http://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(reddit, json)));
};
}
When the promise resolves, you should dispatch another action to update the state with the successful result or the error.

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