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.
Related
Suppose that I have these 2 actions (as an example) for "creating category" and "loading all categories". I need to load all categories every time I create a new category successfully, so, I need to call "loadAllCategories" action within "createCategory". I usually do that like this while using TypeScript with Redux:
// Loading all categories
export const loadAllCategories = () => async (dispatch: Dispatch) => {
try {
// omitted for brevity
dispatch<ILoadAntdTreeSelectCompatibleCategoriesAction>( {
type: TaxonomyActionTypes.LOAD_ANTD_TREESELECT_COMPATIBLE_CATEGORIES,
payload: {
catTreeSelectLoading: false,
catTreeSelectRegistry
}
})
} catch (error) {
// omitted for brevity
}
}
// Creating a category
export const createCategory = (taxonomy: ITaxonomy) => async (dispatch: Dispatch) => {
try {
await agent.Taxonomies.create(taxonomy);
dispatch<any>(loadAllCategories()); <--- Dispatching above action within this one
dispatch<ICreateCategoryAction>({
type: TaxonomyActionTypes.CREATE_CATEGORY,
payload: {
loadingInitial: false
},
})
} catch (error) {
// omitted for brevity
}
}
I wanted to know, using dispatch with "any" type is the only way to call another action within the current one or there is a better way of doing that?
Could I use a more specific type instead of "any"?
Needless to say without using dispatch(action), just by calling the action's name it doesn't change the state so we have to use dispatch.
What is the best practice for doing that?
There is a simpler way to do this when you create a category lets say you use an API for that, make that API return the value you added, in response, then add that category to category list in Redux. use the following function in the reducer.
const addToList = (oldList:any, doc:any) => {
let newList:any = oldList;
newList.push(doc);
return newList;
}
and in the reducer function call it like
case TaxonomyActionTypes.CREATE_CATEGORY:
return { ...state, categories: addToList(state.categories, action.payload) }
Edit
The Answer to your question is
dispatch<Array>
Example
interface Category {
name: String,
}
let x:Array<Category>
I have made some research about possible ways to do it, but I can't find one that uses the same architecture like the one in the app I'm working on. For instance, React docs say that we should have a method which makes the HTTP request and then calls actions in different points (when request starts, when response is received, etc). But we have another approach. We use an action which makes the HTTP call and then dispatches the result. To be more precise, my use case is this:
// action to get resource A
getResourceA () {
return dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
};
}
// another action which needs data from resource A
getSomethingElseByIdFromA (aId) {
return async dispatch => {
const result = await axiosClient.get(`someLink/${aId}`);
dispatch({
type: GET_SOMETHING_BY_ID_FROM_A,
payload: result
});
};
}
As stated, the second action needs data from the first one.
Now, I know of two ways of doing this:
return the result from the first action
getResourceA () {
return async dispatch => {
const result = await axios.get('someLink');
dispatch({
type: GET_RES_A,
payload: result
});
return result;
};
}
// and then, when using it, inside a container
async foo () {
const {
// these two props are mapped to the getResourceA and
// getSomethingElseByIdFromA actions
dispatchGetResourceA,
dispatchGetSomethingElseByIdFromA
} = this.props;
const aRes = await dispatchGetResourceA();
// now aRes contains the resource from the server, but it has not
// passed through the redux store yet. It's raw data
dispatchGetSomethingElseByIdFromA(aRes.id);
}
However, the project I'm working on right now wants the data to go through the store first - in case it must be modified - and only after that, it can be used. This brought me to the 2nd way of doing things:
make an "aggregate" service and use the getState method to access the state after the action is completed.
aggregateAction () {
return await (dispatch, getState) => {
await dispatch(getResourceA());
const { aRes } = getState();
dispatch(getSomethingElseByIdFromA(aRes.id));
};
}
And afterward simply call this action in the container.
I am wondering if the second way is all right. I feel it's not nice to have things in the redux store just for the sake of accessing them throughout actions. If that's the case, what would be a better approach for this problem?
I think having/using an Epic from redux-observable would be the best fit for your use case. It would let the actions go throughout your reducers first (unlike the mentioned above approach) before handling them in the SAME logic. Also using a stream of actions will let you manipulate the data throughout its flow and you will not have to store things unnecessary. Reactive programming and the observable pattern itself has some great advantages when it comes to async operations, a better option then redux-thunk, sagas etc imo.
I would take a look at using custom midleware (https://redux.js.org/advanced/middleware). Using middleware can make this kind of thing easier to achieve.
Something like :
import {GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR } from '../actions/actionTypes'
const actionTypes = [GET_RESOURCE_A, GET_RESOURCE_B, GET_RESOURCE_A_SUCCESS, GET_RESOURCE_A_ERROR ]
export default ({dispatch, getState}) => {
return next => action => {
if (!action.type || !actionTypes.includes(action.type)) {
return next(action)
}
if(action.type === GET_RESOURCE_A){
try{
// here you can getState() to look at current state object
// dispatch multiple actions like GET_RESOURCE_B and/ or
// GET_RESOURCE_A_SUCCESS
// make other api calls etc....
// you don't have to keep stuff in global state you don't
//want to you could have a varaiable here to do it
}
catch (e){
} dispatch({type:GET_RESOURCE_A_ERROR , payload: 'error'})
}
}
}
Is it a good practice to use async/await directly in React component then store the result in the store ? For example:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentWillMount() {
this.getUser();
}
async getUser() {
try {
const user = await userAction.get();
this.props.storeUser(user);
} catch (err) {}
}
}
const state2props = (state) => ({
user: state.User.user
});
const dispatch2props = dispatch => ({
storeUser: (user) => dispatch(userReducer.store(user)),
});
export default connect(state2props, dispatch2props)(User);
It seems more flexible than the classic react/redux pattern.
Yes, you can use async/await in react components. It's not a bad practice
It's just architecture question.
There are lots of ways to implement async logic in applications. In small application you can implement async logic in react components. When your application grow up, you will get some issues like duplicated code (for example you want to fetch user in several react components), code composition and code splitting.
You can use redux-thunk https://github.com/gaearon/redux-thunk, redux-saga https://github.com/redux-saga/redux-saga, redux-logic https://github.com/jeffbski/redux-logic or any other solution.
Moreover you can create your own custom middleware such as:
const reactions = {};
export const addReactions = signals => {
reactions = { ...reactions, ...signals };
};
export default (signalMiddleware = ({ getState, dispatch }) => next => action => {
if (!action.signal) {
return next(action);
}
if (!reactions[action.signal]) {
throw new Error(`There is no handler for ${action.signal} signal`);
}
reactions[action.signal]({ getState, dispatch, payload: action.payload });
});
Such middleware allows you implement business logic into separate layer. For example:
import { addReactions } from './path/to/signalMiddleware';
// Describe your Actions for middleware:
const fetchUser = (id) => ({
signal: 'FETCH_USER',
payload: id
});
const anotherAction = () => ({
signal: 'SOME_ANOTHER_ACTION_WITH_USER',
});
// Describe your business logic using middleware:
addReactions({
FETCH_USER: async ({dispatch}, {id}) => {
const user = await fetcher.get(id);
dispatch({
type: 'RECEIVE_USER',
payload: user,
});
},
SOME_ANOTHER_ACTION_WITH_USER: () => {
// do some awesone job :)
}
})
So our react component could be:
class User extends Component {
render() {
return <div>{this.props.user.name}</div>
}
componentDidMount() {
this.props.dispatch(fetchUser(123));
}
}
export default connect(state2props, dispatch2props)(User);
Now you can divide your application architecture into 3 layer:
1) View — react-components
2) Business logic — your middleware
3) Data logic — your reducer
Between view and business layer we use specific actions with signal field and without type field.
Between business and data logic we use actions with type field.
This architecture allows you to get a strict separation of layers. This architecture is useful in big applications.
In small application it's ok to use redux-thunk or write async logic in react-components.
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.
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.