Calling another action after one is completed in React - javascript

I'm using Redux Thunk.
I got an async operation (updating message to db), and I want to wait for it to complete and then get the updated messages array from the db.
I tried:
const handleWriteMessage = async (e) => {
e.preventDefault()
await dispatch(
writeMessage({
sender: data.sender,
subject: data.subject,
receiver: data.receiver,
message: data.message,
created: date
})
)
dispatch(getMessages())
}
But it doesn't mind the await and runs getMessages() immediately when handleWriteMessage is called.
I tried to do it in the action itself after it's completed:
axios
.post('http://localhost:4000/api/messages/writeMessage', msg, config)
.then((res) => {
getMessages()
dispatch({
type: WRITE_MESSAGE_SUCCESS
})
})
But it's not working too.
What am I missing?

It seem that handleWriteMessage should not by async, it must return function that will accept dispatch and may execute async function, see redux-thunk docs.
See snippet below and its output.
var thunk = createThunkMiddleware();
var log = (state = [], action) => state.concat(action.message || action.type);
var store = Redux.createStore(log, [], Redux.applyMiddleware(thunk, logger));
(async() => {
store.dispatch(asyncAC('async message 1.a'))
.then(() => asyncAC('async message 1.b'))
.then(store.dispatch);
store.dispatch(syncAC('sync 1'));
await store.dispatch(asyncAC('await async message 2.a'));
store.dispatch(syncAC('sync 2'));
store.dispatch(asyncAC('await async message 3.a'))
.then(() => store.dispatch(asyncAC('then async message 3.b')));
})();
function syncAC(m) {
return {
type: 'log',
message: m
}
}
function asyncAC(m) {
return (dispatch) => {
return new Promise(resolve => setTimeout(resolve, 1000, syncAC(m)))
.then(dispatch);
}
}
// redux-thunk itself
function createThunkMiddleware(extraArgument) {
return function(_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function(next) {
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
// logger middleware
function logger({
getState
}) {
return next => action => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
const returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
<script src="https://unpkg.com/redux#4.0.5/dist/redux.js"></script>

Related

How to pass return value from component to another one

I have a const that assembles a get (in another component) and returns me a verification code, this is: response.data.verifyCode
This component is called through a submit on another component.
I need to get this value in my another const, which is below:
export const sendCode = (id, username) => (dispatch) => {
dispatch({ some code here });
return registerAccount
.sendCode(id, username)
.then((response) => {
dispatch({ payload: response.data.verifyCode });
return response.data;
})
.catch(() => {
return null;
});
};
export const getCodeAndVerify = (id, userCode) => (dispatch) => {
dispatch({ some code here });
const getVerifyCode = // I need to get response.data.verifyCode from sendCode above
// I try to use
// const getVerifyCode = { verifyCode: sendCode() };
// but this returns [object object]
return registerAccount
.getCodeAndVerify(id, userCode, getVerifyCode)
.then(() => {
// some code here
})
.catch(() => {
// some code here
});
};
That is, I need to get the verifyCode from the return from the superior const and use it in the other const, but I'm not sure how to do that. Can someone help me?
Asynchronous actions (I'm assuming thunks) also receive a getState second argument after dispatch. Assuming there's a reducer to handle the verifyCode send code success, you can access the store and retrieve the verifyCode value in getCodeAndVerify.
export const sendCode = (id, username) => (dispatch) => {
dispatch({ some code here });
return registerAccount
.sendCode(id, username)
.then((response) => {
dispatch({
type: 'VERIFY_CODE_SUCCESS', // <-- action object needs type
payload: response.data.verifyCode,
});
return response.data;
})
.catch(() => {
return null;
});
};
export const getCodeAndVerify = (id, userCode) => async (dispatch, getState) => {
dispatch({ type: TYPES.PASS_CREATION_REQUESTED });
const getVerifyCode = getState().path.to.verifycode; // <-- retrieve from state
return registerAccount
.getCodeAndVerify(id, userCode, getVerifyCode)
.then(() => {
// some code here
})
.catch(() => {
// some code here
});
};

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

How to test catch statement in async await Action

Problem
I have an Action which awaits an API function. The happy path in the try is easily testable with my mocked API. However, unsure as to the best way to test and cover the .catch.
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Actions Test
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
await fetchRoles()(mockDispatch);
try {
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
// console.log('fetchRoles error: ', e)
}
});
// Here is the problem test, how do we intentionally cause
// getRoles() inside of fetchRoles() to throw an error?
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
await fetchRoles('throwError')(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});
Mocked API
import rolesJson from './roles.json';
export const getRoles = async test => {
let mockGetRoles;
if (test === 'throwError') {
// console.log('sad')
mockGetRoles = () => {
return Promise.reject({
roles: []
});
};
} else {
// console.log('happy')
mockGetRoles = () => {
return Promise.resolve({
roles: rolesJson
});
};
}
try {
const roles = mockGetRoles();
// console.log('api mocks roles', roles);
return roles;
} catch (err) {
return 'the error';
}
};
^ Above you can see what I tried, which did work, but it required me to change my code in a way that fit the test, but not the actual logic of the app.
For instance, for this test to pass, I have to pass in a variable through the real code (see x):
export const fetchRoles = (x) => async dispatch => {
try {
const response = await getRoles(x);
const roles = response.data;
How can we force getRoles in our mock to throw an error in our sad path, .catch test?
You can mock getRoles API on per-test basis instead:
// getRoles will be just jest.fn() stub
import {getRoles} from '../../shared/services/api';
import rolesJson from '../../shared/services/__mocks__/roles.json';
// without __mocks__/api.js it will mock each exported function as jest.fn();
jest.mock('../../shared/services/api');
it('sets something if loaded successfully', async ()=> {
getRoles.mockReturnValue(Promise.resolve(rolesJson));
dispatch(fetchRoles());
await Promise.resolve(); // so mocked API Promise could resolve
expect(someSelector(store)).toEqual(...);
});
it('sets something else on error', async () => {
getRoles.mockReturnValue(Promise.reject(someErrorObject));
dispatch(fetchRoles());
await Promise.resolve();
expect(someSelector(store)).toEqual(someErrornessState);
})
I also propose you concentrate on store state after a call not a list of actions dispatched. Why? Because actually we don't care what actions in what order has been dispatched while we get store with data expected, right?
But sure, you still could assert against dispatch calls. The main point: don't mock result returned in __mocks__ automocks but do that on peer-basis.
I resolved the test and got the line coverage for the .catch by adding a function called mockGetRolesError in the mock api file:
Thanks to #skyboyer for the idea to have a method on the mocked file.
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
// console.log('ACTION roles:', roles);
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Now in the test for the sad path, I just have to call mockGetRolesError to set the internal state of the mocked api to be in a return error mode.
import {fetchRoles} from '../party-actions';
import rolesJson from '../../shared/services/__mocks__/roles.json';
import {mockGetRolesError} from '../../shared/services/api';
jest.mock('../../shared/services/api');
describe('Roles Actions', () => {
it('should set roles when getRoles() res returns', async () => {
const mockDispatch = jest.fn();
try {
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: rolesJson
});
} catch (e) {
return e;
}
});
it('should return empty roles if error', async () => {
const mockDispatch = jest.fn();
mockGetRolesError();
await fetchRoles()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: 'SET_ROLES',
roles: []
});
});
});

react.js: Create resource with redux-form, rest api and async/await

I'm trying to create new resource with redux form and REST api.
I dispatch createPost action and I want to check if the post was succeeded before continue.
const handleFormSubmit = (values, dispatch) => {
dispatch(createPost(values));
//I want to check here if post was succeeded.
//if status = 200 this.props.history.push('/');
}
export function createPost(values) {
return async function(dispatch) {
let request;
try {
request = await axios.post(`${ROOT_URL}/posts`, values)
} catch(err) {
request = { err };
}
dispatch({
type: CREATE_POST,
payload: request
})
}
}
Return a promise, something like this :
export function createPost(values) {
return function(dispatch) {
return new Promise( async function(resolve, reject){
let request;
try {
request = await axios.post(`${ROOT_URL}/posts`, values)
} catch(err) {
reject(err)
}
dispatch({
type: CREATE_POST,
payload: request
})
resolve(request)
})
}
}
const handleFormSubmit = () => {
dispatch(createPost(values))
.then( res => {
// do yoour stuff on succes
} )
.catch(err => {
// stuff on err
})
}
As seeing your codes, I don't think you need to use promise.
Please try like following:
const getAction = (values) => (dispatch) => {
return axios
.post(`${ROOT_URL}/posts`, values)
.then(
() => {
dispatch({
type: CREATE_POST,
payload: request
})
},
() => {
throw new SubmissionError({
_error: 'Failed'
});
}
);
};
const handleSubmit = (values) => {
return dispatch(getAction(values));
};

Why can't I dispatch an action when a promise resolves inside Redux middleware?

Background
I am writing a piece of Redux middleware that makes an axios request and uses Cheerio to parse the result.
Problem
When the Axios promise resolves and I try to dispatch a fulfilled action the action does not show up in the store's action log in the test.
Middleware
function createMiddleware() {
return ({ dispatch, getState }) => next => action => {
if (isCorrectAction(action)===true){
const pendingAction = {
type: `${action.type}_PENDING`,
payload: action.payload
}
dispatch(pendingAction)
axios.get(action.payload.url)
.then(response => {
let $ = cheerio.load(response.data)
let parsedData = action.payload.task($)
const fulfilledAction = {
type: `${action.type}_FULFILLED`,
payload: {
parsedData
}
}
dispatch(fulfilledAction) // dispatch that is problematic
})
.catch( err => {
});
}
return next(action);
}
}
Test that fulfilled action is dispatched fails
it('should dispatch ACTION_FULFILLED once', () => {
nock("http://www.example.com")
.filteringPath(function(path) {
return '/';
})
.get("/")
.reply(200, '<!doctype html><html><body><div>text</div></body></html>');
const expectedActionTypes = ['TASK', 'TASK_PENDING', 'TASK_FULFILLED']
// Initialize mockstore with empty state
const initialState = {}
const store = mockStore(initialState)
store.dispatch(defaultAction)
const actionsTypes = store.getActions().map(e => e.type)
expect(actionsTypes).has.members(expectedActionTypes);
expect(actionsTypes.length).equal(3);
});
Solution - Promise needs to be returned in the mocha test
The solution is to rewrite the mocha test so that the promise is returned. I mistakenly thought that by using nock to intercept the HTTP request that the promise would become synchronous.
The working test looks like:
it('should dispatch ACTION_FULFILLED once', () => {
nock("http://www.example.com")
.filteringPath(function(path) {
return '/';
})
.get("/")
.reply(200, '<!doctype html><html><body><div>text</div></body></html>');
const store = mockStore();
return store.dispatch(defaultScrapingAction)
.then(res => {
const actionsTypes = store.getActions().map(e => e.type)
expect(actionsTypes).has.members(expectedActionTypes);
expect(actionsTypes.length).equal(3);
})
});

Categories