How to integrate redux-form's onSubmit with redux-api-middleware? - reactjs

I'm writing a React / Redux app using redux-form and redux-api-middleware, and I'm having trouble integrating redux-form's onSubmit function with the RSAA lifecycle.
The redux-form documentation says that the onSubmit handler should return a Promise. Until resolve is called on the promise, the form's submitting prop will be true.
However, in this app my API calls don't currently use promises (e.g. via fetch). I make API calls by dispatching a [CALL_API] RSAA action and reducing redux-api-middleware's response actions.
Problem code
class MyReduxFormContainer extends Component {
render() {
return (
<MyReduxForm submit={this.props.submit} />
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
submit: function(values, dispatch) {
dispatch({
[CALL_API]: {
method: 'PATCH',
types: [
{
type: 'REQUEST',
endpoint: '...',
body: JSON.stringify(values)
},
'SUCCESS',
'FAILURE'
]
}
});
// Problem: redux-api-middleware-style API calls normally don't leverage promises.
// Out of the box, this library doesn't offer a promise to return.
}
}
};
export default connect(
// ...
mapDispatchToProps
)(MyReduxFormContainer)
Possible Solutions
I could pass a promise through the payload RSAA callback, which could then resolve/reject the promise after the API response, but this seems to violate the rule that "action creators should't cause side-effects." Granting that redux-api-middleware seems to bend this rule.
I could in theory just use fetch inside the onSubmit handler, instead of redux-api-middleware, but this isn't just a concession which makes my API interactions inconsistent across the application, it also risks duplicating any API middleware activities I've baked in, e.g. setting default headers, de-camelizing / camelizing payloads, etc.
Does anyone have experience using redux-form and redux-api-middleware together?
If it were just redux-api-middleware, I would have expected to simply change the form's submitting prop by altering the form's state when reducing the ACTION_TYPE_[REQUEST|SUCCESS|FAILURE] action types. But it seems non-standard and potentially risky to directly modify the form's state from a reducer. Example redux-form implementations seem to emphasize that redux-form state should be transparent / only indirectly manipulated.
Any thoughts / pointers would be greatly appreciated!
Related GitHub issues
redux-api-middleware:
https://github.com/agraboso/redux-api-middleware/issues/21
https://github.com/agraboso/redux-api-middleware/issues/53
redux-form:
https://github.com/erikras/redux-form/issues/777

Recently I found quite elegant and generic way combine it. Here is article with explanation
export const formToApiAdapter = (dispatch, actionCreator, formatErrors) => (
(...args) => (
new Promise((resolve, reject) => (
dispatch(actionCreator(...args)).then(
(response) => {
if (response.error) {
return reject(formatErrors(response));
}
return resolve(response);
}
)
))
)
);

For lack of a better solution, I'm currently wrapping my dispatch({[CALL_API]}) call inside of a Promise, within the redux-form submit handler.
class MyReduxFormContainer extends Component {
render() {
return (
<MyReduxForm submit={this.props.submit} />
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
submit: function(values, dispatch) {
// Solution: Wrap the [CALL_API] dispatch in a Promise
return new Promise((resolve, reject) => {
dispatch({
[CALL_API]: {
method: 'PATCH',
types: [
{
type: 'MY_PATCH_REQUEST'
endpoint: '...',
body: JSON.stringify(values)
},
{
type: 'MY_PATCH_SUCCESS',
payload: function (action, state, res) {
// Solution: resolve() the promise in the SUCCESS payload callback
// Changes `submitting` prop of MyReduxForm
resolve();
}
},
{
type: 'MY_PATCH_FAILURE',
payload: function (action, state, res) {
// Solution: reject() the promise in the FAILURE payload callback
// Changes `submitting` prop of MyReduxForm
reject();
}
}
]
}
});
});
}
}
};
export default connect(
// ...
mapDispatchToProps
)(MyReduxFormContainer)
Ultimately I'm pretty unhappy with this code architecture, and at this point I think standard fetch usage would have been preferable to redux-api-middleware.
Triggering effects after API responses is standard enough as a concern, there ought to be more elegant solutions than this kind of callback nesting, e.g. using a promise chain.

Related

React Redux best practices for performing actions

Where should I perform actions (redirecting or adding/removing something to/in the localstorage) in React (and Redux)? So after the password is successfully updated I want to redirect the user to another page. Should I redirect after the dispatch method, should I do it in the component or are there other options?
Example action:
export function updateAccountPassword(encryptedPassword) {
return dispatch => {
axios.post(API_URL + '/account/recovery/update', {
_id: getSignedInUserID(),
password: encryptedPassword
}).then(() => {
dispatch(updateUserPasswordSuccess())
}).catch(() => {
dispatch(updateUserPasswordFailError());
})
}
}
function updateUserPasswordSuccess() {
return({
type: RECOVERY_UPDATE_SUCCESS
})
}
function updateUserPasswordFailError() {
return({
type: RECOVERY_UPDATE_FAIL_ERROR,
payload: 'Something went wrong, please try again'
})
}
The way I am doing it is by passing the this.props.history.push as a callback to the action creator, and calling it, as you suggested, in the action creator, after dispatch.
Here is an example from my code:
In the component form's submission, calling the action creator:
this.props.ACTION_CREATOR(formPayload, () => {
this.props.history.push(`ROUTING_TARGET`);
});
And, then, in the action creator, when the proper condition has been met, calling the callback (rerouting).

Redux axios request cancellation

I have a React Native application with Redux actions and reducers. I'm using the redux-thunk dispatch for waiting the asyncron calls. There is an action in my application:
export const getObjects = (id, page) => {
return (dispatch) => {
axios.get(`URL`)
.then(response => {
dispatch({ type: OBJECTS, payload: response });
}).catch(error => {
throw new Error(`Error: objects -> ${error}`);
});
};
};
That's working properly, but sometimes the user click on the back button before the action finished the request, and I must cancel it. How can I do it in a separated action? I read this, but I didn't find any option in axios for abort. I read about the axios cancellation, but it's create a cancel method on the function scope and I can't return, because the the JS don't support multiple returns.
What is the best way to cancel axios request in an other Redux action?
I would recommend using something like RxJS + Redux Observables which provides you with cancellable observables.
This solution requires a little bit of learning, but I believe it's a much more elegant way to handle asynchronous action dispatching versus redux-thunk which is only a partial solution to the problem.
I suggest watching Jay Phelps introduction video which may help you understand better the solution I'm about to propose.
A redux-observable epic enables you to dispatch actions to your store while using RxJS Observable functionalities. As you can see below the .takeUntil() operator lets you piggyback onto the ajax observable and stop it if elsewhere in your application the action MY_STOPPING_ACTION is dispatched which could be for instance a route change action that was dispatched by react-router-redux for example:
import { Observable } from 'rxjs';
const GET_OBJECTS = 'GET_OBJECTS';
const GET_OBJECTS_SUCCESS = 'GET_OBJECTS_SUCCESS';
const GET_OBJECTS_ERROR = 'GET_OBJECTS_ERROR';
const MY_STOPPING_ACTION = 'MY_STOPPING_ACTION';
function getObjects(id) {
return {
type: GET_OBJECTS,
id,
};
}
function getObjectsSuccess(data) {
return {
type: GET_OBJECTS_SUCCESS,
data,
};
}
function getObjectsError(error) {
return {
type: GET_OBJECTS_ERROR,
data,
};
}
const getObjectsEpic = (action$, store) = action$
.ofType(GET_OBJECTS)
.switchMap(action => Observable.ajax({
url: `http://example.com?id=${action.id}`,
})
.map(response => getObjectsSuccess(response))
.catch(error => getObjectsError(error))
.takeUntil(MY_STOPPING_ACTION)
);

Can action creator be a class with action methods

Generally in Redux (thunk), action creators are static methods. However I have a particular scenario where action creators need to make an async call to different endpoints. One way to do it would be to pass in a flag to determine which endpoint to go to:
function _getEndPoint(isHouse) {
return isHouse ? 'house' : 'flat';
}
export function post(model, isHouse = false) {
return (dispatch, getState) => {
dispatch({
types: [POST, POST_SUCCESS, POST_FAILED],
promise: (client) => client.post(`${_getEndPoint(isHouse)}`, {
data: model,
}),
});
};
}
But is there a way to create a class say Property with post as a public method which serves as action method. So when I instantiate the Property class, I define the type of the property and set the right endpoint on creation?
Coming from a C# background, I was also wondering if I can use something like Generics or inheritance to solve this problem.
Also, is this a good practice to attempt create action methods with classes as I read that Redux is more into functional style of programming.
If you have to make calls to different endpoints (asynchronously or not) – these are different actions. Say you want to handle error. Generic type POST_FAILED will not tell you which resource has failed. What if you want to change state tree based on it? What if different types should be handled in various ways? Functional approach POST_OF_SOMETHING_FAILED suits better here. Small functions do their small things. Otherwise you would have to use complex if block.
Instead of using classes and inheritance you could implement some kind of action generator to get rid of code duplication. For example if you want to create shared CRUD actions, this is how create action might look like:
/actions/generators/create.js
import fetch from 'isomorphic-fetch';
function requestCreateItem(modelName) {
return {
type: `CREATE_${modelName.toUpperCase()}_REQUEST`,
};
}
function receiveCreateItem(modelName, item) {
return {
type: `CREATE_${modelName.toUpperCase()}_SUCCESS`,
item,
receivedAt: Date.now(),
};
}
function catchCreateItemError(modelName, error) {
return {
type: `CREATE_${modelName.toUpperCase()}_ERROR`,
error,
receivedAt: Date.now(),
};
}
function createItem(modelName) {
return (dispatch) => {
dispatch(requestCreateItem(modelName));
return fetch(`/${modelName}s`, {
method: 'POST',
credentials: 'same-origin',
}).then(response => response.json()).then(json => {
if (json.error) {
dispatch(catchCreateItemError(modelName, json.error));
} else {
dispatch(receiveCreateItem(modelName, json));
}
}).catch(error => {
dispatch(catchCreateItemError(modelName, error));
});
};
}
export default function create(modelName) {
return () => createItem(modelName);
}
You can use it inside actions index file as a constructor.
/actions/index.js
import create from './generators/create';
const createBot = create('bot');
export const botActions = { createBot };
This is the main idea.

redirect from component level after specific dispatch - redux thunk

I have a fairly simple use case, but having a hard to find the appropriate answer. I'm using React,Redux,React Router & redux thunk middleware.
Lets say, I have two module food-tags & food. These modules have individual create,list,edit page/component. In practical use case, food-tags have no special value. Whenever a food object is created, separated tags are inserted into the food object's tags property.
General use case is that, after any item is created successfully, react router redirects it to the list page.
whenever i'm calling the createTag action from food-tag module, I can do it in a hacky way. like just after the success dispatch, i can call
browserHistory.push('/dashboard/tags')
this leads me to a problem where i can create food-tag inline from the food create component. Codes are given below
actions.js
export function createTag(tag) {
return function (dispatch) {
axios.post(API_URL + 'api/tags', tag)
.then((response) => {
// I CAN DO REDIRECT HERE,BUT THIS CAUSES THE PROBLEM
dispatch({type: 'TAG_CREATE_RESOLVED', payload:response});
toastr.success('Tag created Successfully.......!');
})
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err});
toastr.warning(err.message);
})
}
}
component/container.js
createTag () {
//validatation & others....
this.props.createTag(tag)
}
react-redux connection
function mapDispatchToProps (dispatch) {
return bindActionCreators({
createTag: createTag
}, dispatch)
}
Almost same pattern in food/create.js
$('#food-tags').select2(select2settings).on('select2:selecting', function (event) {
let isNewTagCreated = event.params.args.data.newOption,
name = event.params.args.data.text;
if (isNewTagCreated && name !== '') {
reactDOM.props.createTag({name}); // reactDOM = this context here
}
});
What I want basically that, I want to get access in the component level which action type is dispatching so that i can redirect from component & show notifications as well instead of action thunk. May be i'm not thinking in the proper way. there could be a dead simple work around.
It's good to know that redux-thunk passed out return value from the function. So you can return the promise from the action creator and wait until it will be finished in you component code
export function createTag(tag) {
return function (dispatch) {
return axios.post(API_URL + 'api/tags', tag) // return value is important here
.then((response) => dispatch({type: 'TAG_CREATE_RESOLVED', payload:response}))
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err})
throw err; // you need to throw again to make it possible add more error handlers in component
})
}
}
Then in your component code
createTag () {
this.props.createTag(tag)
.then(() => {
toastr.success('Tag created Successfully.......!');
this.props.router.push() // I assume that you have wrapped into `withRouter`
})
.catch(err => {
toastr.warning(err.message);
});
}
Now you have proper split up between action logic and user interface.

Is it a good idea to use browserhostory.push in action helpers?

In my React App I need to take decision based on data I receive from the server.
If data is expected ( Dispatch actions to update state)
If data has error tag ( browserhistory.push('/notfound'); )
If expected data is unable to parsed ( browserhistory.push('/error');)
In my app structure, I am using Redux, React-Router and React-redux-Router libraries but no middleware. I have made actionHelpers to making ajax calls and then dispatch appropriate actions using Action Creator. These actionHelper methods are exposed in Components to change state.
My Questions:
What's the best way to handle these scenarios ?
Is actionHelper the best place to take these decisions ?
I don't want to use any middleware for now but please let me know if its a good idea to use middleware to handle these scenarios.
Actions are not the place where you should do redirections. This behavior should be implemented in the component itself and actions should be left to update the store.
You may want to use the Redux-thunk middleware here which allows you to dispatch a function (which receives dispatch as an argument instead of the object actions. You can then wrap that function in a promise and use it in componentWillMount.
In your actions file:
updateReduxStore(data) {
return {
type: SOME_TYPE,
payload: data.something
};
}
fetchAndValidateData() {
...
}
checkData() {
return function(dispatch) {
return new Promise((resolve, reject) => {
fetchAndValidateData().then((data) => {
try {
if (JSON.parse(data).length > 0) {
dispatch(updateReduxStore(data));
resolve('valid data');
} else if (data.error) {
reject('error in data');
}
}
catch(err) {
reject('malformed data');
}
});
});
};
}
Then in your component:
componentWillMount() {
this.props.checkData()
.then((message) => {
console.log(message); //valid data
})
.catch((err) => {
if (err === 'error in data') {
browserHistory.push('/notfound');
} else if (err === 'malformed data') {
browserHistory.push('/error');
}
});
}
Redux-thunk middleware is made for such use cases.

Resources