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())
})
}
Related
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
The below code dispatches create_event and then call addFlashMessage action
export const createEvent = event => {
return dispatch => {
dispatch({
type: "CREATE_EVENT",
payload: {
event
}
});
addFlashMessage("Event has been created");
};
};
the addFlashMessage is called at action
But ADD_FLASH_MESSAGE is not called
export const addFlashMessage = message => {
console.log("action is invoked");
return dispatch => {
dispatch({
type: "ADD_FLASH_MESSAGE",
payload: {
message
}
});
};
};
This is never being called
case "ADD_FLASH_MESSAGE":
console.log("action called");
Instead of just calling another function from createEvent you need to dispatch in order to trigger the redux-thunk function.
Here is the corrected one.
export const createEvent = event => {
return dispatch => {
dispatch({
type: "CREATE_EVENT",
payload: {
event
}
});
dispatch(addFlashMessage("Event has been created"));
};
};
You need to dispatch the action to invoke addFlashMessage like following.
dispatch({type: "ADD_FLASH_MESSAGE",payload:"Event has been created"});
Your code should look like followng.
export const createEvent = event => {
return dispatch => {
dispatch({
type: "CREATE_EVENT",
payload: {
event
}
});
dispatch({type: "ADD_FLASH_MESSAGE",payload:"Event has been created"});
};
};
I'm using react redux to create an action creator in my app. The point is that when I use async await syntax, it auto returns a promise (without the "return" keyword). However, when I use old-style promise like then(), i have to explicitly type the "return" keyword - otherwise it will return undefined. Why does this happen?
app.js (createStore):
app.get('*', (req, res) => {
const store = createStore(reducers, applyMiddleware(reduxThunk));
const promise = matchRoutes(RouteApp, req.path).map(({ route }) => {
return route.loadData ? route.loadData(store) : null;
});
console.log(promise);
Promise.all(promise).then(() => {
res.send(renderApp(req, store));
});
});
route.js:
export default [
{
loadData,
path: '/',
component: Landing,
exact: true,
},
];
landing.js
function loadData(store) {
return store.dispatch(fetchUser());
}
export { loadData };
When I use async await:
action.js
export const fetchUser = () => async (dispatch) => {
const res = await axios.get('https://react-ssr-api.herokuapp.com/users');
dispatch({
type: INFO_USER,
payload: res.data,
});
};
When I use promise then:
// It doesn't work
export const fetchUser = () => (dispatch) => {
axios.get('https://react-ssr-api.herokuapp.com/users').then((res) => {
dispatch({
type: INFO_USER,
payload: res.data,
});
});
};
"return" keyword
// now it works
export const fetchUser = () => (dispatch) => {
return axios.get('https://react-ssr-api.herokuapp.com/users').then((res) => {
dispatch({
type: INFO_USER,
payload: res.data,
});
});
};
async function always returns a promise, that's its purpose. In case there's no return value, it returns a promise of undefined.
As the reference states,
Return value
A Promise which will be resolved with the value returned by the async
function, or rejected with an uncaught exception thrown from within
the async function.
This async function
export const fetchUser = () => async (dispatch) => {
const res = await axios.get('https://react-ssr-api.herokuapp.com/users');
dispatch({
type: INFO_USER,
payload: res.data,
});
};
is syntactic sugar for this function:
export const fetchUser = () => (dispatch) => {
return axios.get('https://react-ssr-api.herokuapp.com/users').then((res) => {
dispatch({
type: INFO_USER,
payload: res.data,
});
});
};
Trying to use props from <button> of component in the dispatch of a redux-thunk function that has been set up for Async process but I can't quite get how to use both props and the function (that's being connected to the component through react-redux connect in the mapDispatchToProps) but I just can't figure out how to call both the props and the function.
function loadData(dispatch, medium) {
console.log(dispatch)
return dispatch({type: 'LOADING_COMPONENT'})
return axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : () => dispatch(loadData()),
}
}
const LoginButtons = ({props, LogInClick}) => (
<button onClick={() => LogInClick(props.medium)} type="button">{props.buttonName}</button>
)
const LoginConnect = connect(null, mapDispatchToProps)(LoginButtons)
And Then I export that and try to call it so it can be reused in the render file like
<LoginConnect medium='suhhhh' buttonName='To log in to suhhh'/>
function loadData(dispatch, medium) {
console.log(dispatch)
return dispatch({type: 'LOADING_COMPONENT'})
return axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : () => dispatch(loadData()),
}
}
const LoginButtons = ({medium, buttonName, LogInClick}) => (
<button onClick={() => LogInClick(medium)} type="button">{buttonName}</button>
)
const LoginConnect = connect(null, mapDispatchToProps)(LoginButtons)
This should work !! actually connect merges mapStateToProps, mapDispatchToProps into this.props. Read this documenation for better understanding https://github.com/reactjs/react-redux/blob/master/docs/api.md
Try returning a function, which redux-thunk will then call with dispatch as an argument.
You can then call dispatch from that returned function:
function loadData(medium) {
return (dispatch) => {
dispatch({ type: 'LOADING_COMPONENT' })
axios.get(`/professionals/${medium}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
)
}
}
Your LogInClick function can then take an argument which can be passed into loadData:
const mapDispatchToProps = (dispatch) => {
return {
LogInClick: (medium) => dispatch(loadData(medium)),
}
}
const LoginButtons = (props) => (
<button onClick={() => props.LogInClick(props.medium)} type="button">{props.buttonName}</button>
)
I hope this helps.
I was testing redux actions using Jest , When i try to test the default action, it throws an Error
Expected value to equal:
{"payload": {"male": "mancha"}, "type": "actions/change_gender"}
Received:
[Function anonymous]
It seems it sends the function, instead of values.
test change_gender.js
import changeGender, { CHANGE_GENDER } from '../change_gender';
const payload = {
type: CHANGE_GENDER,
payload: {
male: 'mancha'
}
};
describe('actions', () => {
it('should Change the ', () => {
const expectedAction = {
type: payload.type,
payload: payload.payload
};
expect(changeGender('male', 'mancha')).toEqual(expectedAction)
});
});
Action change_gender.js
import toggleToolTip from './toggle_tooltip'; // eslint-disable-line
export const CHANGE_GENDER = 'actions/change_gender';
export default(radioType, type) => (dispatch) => {
dispatch({
type: CHANGE_GENDER,
payload: {
[radioType]: type
}
});
};
You should return the dispatch at change_gender.js:
change_gender.js:
import toggleToolTip from './toggle_tooltip'; // eslint-disable-line
export const CHANGE_GENDER = 'actions/change_gender';
export default(radioType, type) => (dispatch) => {
return dispatch({
type: CHANGE_GENDER,
payload: {
[radioType]: type
}
});
};
As Chen-tai mentioned, returning from the dispatch would help here for testing purposes.
The reason you see [Function] being returned is that your action is a function returning a function.
(radioType, type) => (dispatch) => { ... }
The first set of params, followed by the fat arrow is an anonymous function. That then returns another anonymous function that takes a dispatch function as its arguments. So, if we call the action twice, providing a mock dispatch function, we'll get back the expected action!
const action = (radioType, type) => (dispatch) => {
return dispatch({
type: "CHANGE_GENDER",
payload: {
[radioType]: type
}
});
};
console.log(
action('male', 'mancha')((action) => action)
)
We can then write out test:
Action change_gender.js
import toggleToolTip from './toggle_tooltip'; // eslint-disable-line
export const CHANGE_GENDER = 'actions/change_gender';
export default(radioType, type) => (dispatch) => {
return dispatch({
type: CHANGE_GENDER,
payload: {
[radioType]: type
}
});
};
test change_gender.js:
import changeGender, { CHANGE_GENDER } from '../change_gender';
const payload = {
type: CHANGE_GENDER,
payload: {
male: 'mancha'
}
};
describe('actions', () => {
it('should Change the ', () => {
const expectedAction = {
type: payload.type,
payload: payload.payload
};
expect(changeGender('male', 'mancha')((payload) => payload).toEqual(expectedAction)
});
});