Redux - how to combine multiple mapDispatchToProps? - reactjs

I'm developing a react-native / redux app, it contains a few Reducer files that I'm able to combine using combineReducers(). In order to manage the code and keep it maintainable, these files also contain mapDispatchToProps entries (I've done this because dispatch functions are closely related to Reducers) something like:
export const counterMapDispatchToProps = dispatch => {
return {
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
}
}
and
export const statusMapDispatchToProps = dispatch => {
return {
setStatus: (text) => dispatch({ type: 'SET_STATUS', status: text }),
}
}
If possible I'd like to take the outputs of these mapDispatchToProps and combine them so that I get a function that produces the following, which can then be used when connecting to my Redux store:
export const appMapDispatchToProps = dispatch => {
return {
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
setStatus: (text) => dispatch({ type: 'SET_STATUS', status: text }),
}
}
I guess my question effectively comes down to: how do I write a combineMapDispatchToProps({counterMapDispatchToProps, statusMapDispatchToProps, ...}) function?

Since these functions just return an object (which is then handled by the Consumer HOC) you can use ES6 spreading -
export const appMapDispatchToProps = dispatch => {
return {
...counterMapDispatchToProps(dispatch),
...statusMapDispatchToProps(dispatch),
}
}
You can add more functions to this, and of course import them from other files.
If you need ES5, you can use Object.assign to similar effect:
export const appMapDispatchToProps = function(dispatch) {
return Object.assign(counterMapDispatchToProps(dispatch), statusMapDispatchToProps(dispatch));
}

Related

Difference between returning dispatch function or returning a

I'm learning redux and i'm reading a react/redux source-code. in actions/Video.js file it has this code:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
export const getVideoInfo = info => {
return { type: GET_VIDEO_INFO, payload: info };
};
So what's the difference between:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
and
export const startDownloading = url => {
ipcRenderer.send("video:url", { url });
return {
type: START_DOWNLOADING
};
};
I mean, when we should return an object and when we should call dispatch function in an action file?
the difference relies on how to fire a change in your store,
by default calling an action will not fire the event that is going to be handle by the reducer
so you call dispatch either and object or a function
function
export const someFunction = () => ({type: 'actioncreator'})
dispatch(someFunction())
object
dispatch({type: 'actioncreator'}
you can dispatch actions inside a component or inside a actions folder

React Redux -possible to have a call back in dispatch function

Guys i am having some trouble or quite doubtful.
am having one component and one reducer.
Reducer.js
import {
ASSET_POPUP_GET_ENDPOINT,
} from 'apiCollection';
import { performGet } from 'services/rest-service/rest-service';
export const GET_ASSETS_LIST = 'stories/GET_ASSETS_LIST';
const initialState = {
imgGroup: [],
isLoading: false,
};
const modalUploadReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSETS_LIST: {
return {
...state,
ImageJson:action.payload.imageGroup,
};
}
case GET_ASSETS_LIST_ERROR: {
return {
...state,
isLoading:false,
};
}
default:
return state;
}
};
export const getModalClose = () => (dispatch) => {
dispatch({ type: CLOSE_MODAL });
}
export const getListActionDispactcher = () => (dispatch) => {
performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
export default modalUploadReducer;
and my component look like
it do have mapStateToProps and mapDispatchToProps
and one of the function
const mapDispatchToProps = dispatch => ({
getCollection: () => dispatch(getListActionDispactcher()),
});
addDocumentClick = () =>{
this.props.getAssetsCollection();
}
and is it possible to have some setState/manipulation of response after api response got from reducer in the component
based on the response i need to do some changes in addDocumentClick.
Means something like this
addDocumentClick = () =>{
this.props.getAssetsCollection().then(...based on response;
}
The correct way for solving this is setting a global loading flag and in your componentDidUpdate() method, checking for the value to determine that the action has just succeeded. You already seem to have the isLoading flag. Just set it when the action's dispatched, and unset it after it succeeds/fails. And in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.isLoading && !this.props.isLoading) {
// do something
}
}
Of course, you need to connect() your loading flag to your component to achieve this.
If all you care about is whether the assets list has changed, you can simply check for the change of that prop in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.ImageJson !== this.props.ImageJson) {
// do something
}
}
Another solution is sending a callback to your action dispatcher, which makes your code more tightly coupled and I don't recommend, but it does work too. So, when you connect(), you can:
getCollection: (onSuccess) => dispatch(getListActionDispactcher(onSuccess)),
In your action dispatcher:
export const getListActionDispactcher = (onSuccess) => (dispatch) => {
// ...once API finished/failed
onSuccess(someData);
}
Finally, in your component:
this.props.getCollection((result) => {
console.log('succeeded!', result);
// hide modal, etc..
}
You are using redux-thunk, and calling thunk will return a promise which will resolve in whatever you return in your thunk. Therefore, all you need to do is to add return value to getListActionDispactcher
export const getListActionDispactcher = () => (dispatch) => {
// return this promise
return performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
// return whatever you want from promise
return payload
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
.
addDocumentClick = () => {
this.props.getAssetsCollection().then(payload => console.log(payload))
}
You should, however, look for ways to avoid this pattern to have your components decoupled from actions as much as possible for the sake of modularity

How to wait for dispatch to complete using thunk middleware?

I am using redux-thunk and want like to dispatch an action and once that is finished make an api call with part of that updated store.
store.js
const middleware = composeEnhancers(applyMiddleware(promise(), thunk, logger()))
const localStore = loadStore()
const store = createStore(reducer, localStore, middleware)
graphActions.js:
First add an Element:
export function addElement(element) {
return dispatch => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
}
}
Then make api call via different action creator:
export function saveElements() {
return (dispatch, getState) => {
let graphId = getState().elements.id
let elements = getState().elements.elements
axios.put(Config.config.url + '/graph/' + graphId, {
'data': JSON.stringify({elements: elements}),
}).then(() => {
dispatch({type: SHOW_SUCCESS_SNACKBAR})
}).catch((err) => {
dispatch({type: SHOW_ERROR_SNACKBAR})
dispatch({type: UPDATE_ELEMENTS_REJECTED, payload: err})
})
}
}
I need to make sure, that addElement() is finished before saveElements(), so that saveElements() accesses the updated store.
I tried the following:
export function addElement(element) {
const promise = (dispatch) => new Promise((resolve) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
resolve()
})
return dispatch => {
promise(dispatch).then(() => {
saveElements()
})
}
}
ADD_ELEMENT is dispatched, but the actions within saveElements() are not dispatched, no api call is made.
I was missing to dispatch saveElements() and returning dispatch(saveElements()).
export function addElement(element) {
const promise = (dispatch) => new Promise((resolve) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
resolve()
})
return (dispatch) => {
return addElements(dispatch).then(() => {
return dispatch(saveElements())
})
}
UPDATE:
Noticed I can simply do:
export function addElement(element)
return (dispatch) => {
dispatch({
type: ADD_ELEMENT,
payload: element
})
dispatch(saveElements())
})
}

Actions must be plain objects. Use custom middleware for async actions How to dispatch action

My goal is dispatch one action after another. First the actionOne should be dispatched and next the actionTwo should be dispatched. I am very new to redux.
action.js
export const actionOne = (value) => ({
type: Explore.ACTION_ONE,
payload: { value },
});
export const actioneTwo = payload => ({
type: Explore.ACTION_TWO,
payload,
});
reducer.js
case Explore.ACTION_ONE: {
return {
...state,
tabs: somefunction(state),
checkFlag: true
};
}
case Explore.ACTION_TWO: {
return {
...state,
checkFlag: false
};
}
There is another container(in its epic.js) where I call the above action
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT).mergeMap(action => {
return getCount(action.payload) // This returns our Observable wrapping the Promise
.map(response => { //some code
return [actionOne(updatedPayload),actionTwo(updatedPayload)];
})
.catch(error => {
return [getCountRejected(error)];
})
.takeUntil(action$.ofType(AnnotationConstants.GET__COUNT_CANCELLED));
});
I am not able to dispatch actionwTwo and get error "Actions must be plain objects. Use custom middleware for async actions". what is correct way to dispatch after actionOne is finished?
It looks to me that you are returning an observable array, when the epic wants an observable object (or when two actions returned, a sequence of observable object).
This might be the pattern you require Process Manager dispatch multiple actions
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT)
.mergeMap(action => {
return getCount(action.payload)
.flatMap(response => {
//some code
return Observable.concat(
Observable.of(actionOne(updatedPayload)),
Observable.of(actionTwo(updatedPayload))
)
})
.catch(error => {
return [getCountRejected(error)];
})
.takeUntil(action$.ofType(AnnotationConstants.GET__COUNT_CANCELLED));
});
or you might get away with simpler
export const getCountEpic = (action$, store) =>
action$.ofType(Constants.GET__COUNT)
.mergeMap(action => {
return getCount(action.payload)
.map(response => {
//some code
return Observable.of(
actionOne(updatedPayload),
actionTwo(updatedPayload)
)
})
...

mapDispatchToProps to be explicit

In react/redux I'm trying to convert this mapDispatchToProps to be explicit:
const mapDispatchToProps = { createOrganization }
I tried this:
const mapDispatchToProps = (dispatch) => {
return {
createOrganization: (organization) => {
dispatch(createOrganization(organization))
}
}
}
And this is the action
export const createOrganization = (organization) => ({
type: ACTION_CREATE_ORGANIZATION,
payload: api.createOrganization(organization),
})
But It's nor working. What can I do? Am I missing something?
The error is "Cannot read property 'then' of undefined". What it's happening is that once I enter a code, it should create an organization and redirect me to the page /dashboard, but it's not working
handleClick = (e, formData) => {
e.preventDefault()
if (formData.betaCode && formData.organization && this.props.userData) {
this.props.createOrganization({
name: formData.organization,
owner: {
id: this.props.userData.id,
name: this.props.userData.login,
ownerAvatar: this.props.userData.avatar_url
},
beta_code: formData.betaCode
})
.then(() => {
this.props.history.push('/dashboard')
})
}
}
Based on your code, createOrganization action should be an async. Something similar to:
const = createOrganization = organization => dispatch =>
api.createOrganization(organization)
.then(
response => dispatch({ type: ACTION_CREATE_ORGANIZATION, payload: response}),
error => dispatch({ type: ACTION_CREATE_ORGANIZATION_ERROR, payload: error}),
)
But it's not enough, you should install redux-thunk && redux-promise to handle such kind of action.
Rest of your code shouldn't be changed. Then you will be able to use mapDispatchToProps as you want:
const mapDispatchToProps = { createOrganization }
Hope it make sense. Async flow in redux

Resources