Here is origin code:
export function startGame() {
return function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if(response.status===200){
dispatch({
type: TYPE.START_GAME,
});
}
})
.catch((error) => {
dispatch({
type: TYPE.ERROR,
});
});
}
}
what I want is I get the api result first, and then decide what next step I want to do (because I have many actions that all call the same api )
my logic is below, but I don't know how to make it work
Please help me
export function startGame() {
let result = function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if(response.status===200){
return {
"result" : "OK",
"data" : response.data
}
}
})
.catch((error) => {
return {
"result" : "FAIL",
"data" : error
}
});
}
if result.result === "OK" {
dispatch(someAction())
}else{
dispatch(otherAction())
}
}
I'm not sure why you can't just dispatch the someAction and otherAction in your axios callbacks. Why doesn't this work for you?
export function startGame() {
return function(dispatch) {
axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
})
.then(response => {
if (response.status === 200) {
dispatch(someAction(response.data));
}
})
.catch((error) => {
dispatch(otherAction(error));
});
}
}
If you want to define the API calling function elsewhere, you can do this:
// In some other file, say api.js
export function startGameApiCall() {
return axios({
method: 'post',
url: '/api/actions/game/',
data: {'game':'start'},
headers: getHeaders(),
});
}
// In your actions file
import { startGameApiCall } from './api';
export function startGame() {
return function (dispatch) {
startGameApiCall()
.then(response => dispatch(someAction(response.data)))
.catch(() => dispatch(otherAction()));
}
}
I would also look into https://github.com/svrcekmichal/redux-axios-middleware It dispatches another action depending on the result of you axios request.
Related
Action "type": undefined, is what I keep getting returned when I try to test a redux action with fetch-mock. Any suggestion on how to resolve this issue? Could it be a bug in fetch-mock?
Expected value to equal:
[{"type": undefined}, {"result": {"hello": "world"}, "type": undefined}]
Received:
[{"type": "DASHBOARD_RESULT_LOADING"}, {"result": {"hello": "world"}, "type": "DASHBOARD_RESULT_READY"}]
dashboardActions.js
function resultReady(json) {
return {
type: DASHBOARD_RESULT_READY,
result: camelizeKeys(json)
};
}
export function requestPredict(params) {
let url = `${process.env.API_URL}/predict/`;
const requestParams = {
method: 'post',
credentials: 'include'
};
return async (dispatch) => {
return fetch(url, requestParams)
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw Error(response.statusText);
}
})
.then(data => dispatch(resultReady(data)));
};
}
dashboardActions.test.js
const mockData = {
"hello": "world"
}
describe('action creators', () => {
afterEach(() => {
fetchMock.reset()
})
it('should create DASHBOARD_RESULT_LOADING', () => {
fetchMock.post('*', {"hello":"world"} );
const expectedActions = [
{ type: actions.DASHBOARD_RESULT_LOADING },
{ type: actions.DASHBOARD_RESULT_READY, result: mockData }
]
const store = mockStore({ result: {}})
return store.dispatch(actions.requestPredict())
.then((data) => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
You're receiving types "DASHBOARD_RESULT_LOADING" and "DASHBOARD_RESULT_READY", which seems to be the intended behaviour. You're expecting actions.DASHBOARD_RESULT_LOADING and actions.DASHBOARD_RESULT_READY, neither of which seems to be defined with any value.
Either define actions.DASHBOARD_RESULT_LOADING and actions.DASHBOARD_RESULT_READY:
actions.DASHBOARD_RESULT_LOADING = 'DASHBOARD_RESULT_LOADING'
actions.DASHBOARD_RESULT_READY = 'DASHBOARD_RESULT_READY'
or replace them with your expected types:
const expectedActions = [
{
type: 'DASHBOARD_RESULT_LOADING'
},
{
type: 'DASHBOARD_RESULT_READY',
result: mockData
}
]
I think my solution is in this question but I can't get it to work Promise.all behavior with RxJS Observables?
I'm trying to return an observable on two promises via forkJoin.
One promise gets an ID from the server and another processes a file to generate a thumbnail.
export function createSceneFromFile(action$) {
return action$.ofType(CREATE_SCENE_FROM_FILE)
.mergeMap(({locationId,file}) =>
createSceneThumb(locationId,file)
.map((res,preview) => {
console.log(res,preview)
if (res.error) {
return { type: CREATE_SCENE_FAILED, payload: res.message }
} else {
return {type: CREATE_SCENE_SUCCESS, payload: {...res.payload,preview} }
}
})
.catch(err => { return { type: CREATE_SCENE_FAILED, message: err } })
)
}
function createSceneThumb(locationId,file){
const request = fetch(`${API_URL}/location/${locationId}/createscene/${file.name}/`, {
method: 'get',
credentials: 'include',
}).then(res => res.json())
const thumb = fileToScenePreview(file)
return Observable.forkJoin(request,thumb)
}
export function fileToScenePreview(file){
return new Promise((resolve,reject)=>{
getFileThumb(file).then((canvas)=> {
canvas.toBlob((blob) => {
blob.lastModifiedDate = new Date()
blob.name = 'scenepreview_' + file.name + '.png'
const uploader = new S3Upload({
getSignedUrl: getSignedUrl,
uploadRequestHeaders: {'x-amz-acl': 'public-read'},
contentType: 'image/png',
scrubFilename: (filename) => filename.replace(/[^\w\d_\-.]+/ig, ''),
contentDisposition: 'auto',
s3path: 'assets/',
onError:()=>reject,
onFinishS3Put: ()=>resolve(blob.name),
})
uploader.uploadFile(blob)
})
})
})
}
But i never get a response.
Is this the right way of going about it?
I am trying to post data from a redux-form to an api server using axios.My action creator for posting the data looks like this:
//Action creator for submitting edited post
export function submitEditedPost(values, callback) {
const request = axios.post(`${API}/posts`, {headers}, {values});
return dispatch => {
return request.then((data) => {
callback();
console.log(data)
dispatch({
type:SUBMIT_POST,
payload: data
})
})
}
}
My onSubmit() methos of the form where the action-creator is called looks like this:
onSubmit(values) {
var id = Math.random().toString(36).substr(-8);
var d = new Date().toLocaleTimeString();
const formData = {};
for (const field in this.refs) {
formData[field] = this.refs[field].value;
}
formData.id = id;
formData.timestamp = d;
console.log('-->', formData);
this.props.submitEditedPost(formData, () => {
this.props.history.push('/');
});
}
When I try to console.log the edited values,I can see it correctly,but I am not able to post the edited values and update the api. The error message is shown in the screenshot below:
How do I proceed? Can someone please help me with this issue?
EDIT 1: My whole action file:
import axios from 'axios';
export const FETCH_POSTS = 'fetch_posts';
export const CREATE_POST = 'create_post';
export const FETCH_POST = 'fetch_post';
export const DELETE_POST ='delete_post';
export const EDIT_POST = 'edit_post';
export const SUBMIT_POST = 'submit_post';
let token ;
if(!token)
token = localStorage.token = Math.random().toString(36).substr(-8)
const API = 'http://localhost:3001';
const headers = {
'Accept' : 'application/json',
'Authorization' :'token'
}
//Action creator for fetching posts from the API server
export function fetchPosts() {
const URL = `${API}/posts`;
const request = axios.get(URL,{headers});
return dispatch => {
return request.then(({data}) => {
console.log(data);
dispatch({
type : FETCH_POSTS,
payload : data
})
})
}
}
//Action Creator for creating posts
export function createPosts(values, callback) {
return dispatch => {
return axios.post(`${API}/posts`,values,{headers})
.then((data) => {
callback();
console.log(data)
dispatch({
type: CREATE_POST,
payload: data
})
})
}
}
//Action Creator for displaying a selected post
export function fetchPost(id) {
const request = axios.get(`${API}/posts/${id}`,{headers});
return dispatch => {
return request.then(({data}) => {
console.log(data);
dispatch({
type: FETCH_POST,
payload: data
})
})
}
}
//Action creator for deleting post
export function deletePost(id, callback) {
const request = axios.delete(`${API}/posts/${id}`, {headers})
.then(() => callback());
return {
type: DELETE_POST,
payload: id
}
}
//Action creator for editing post
export function editPost(id, callback) {
const request = axios.get(`${API}/posts/${id}`,{headers});
return dispatch => {
return request.then((data) => {
callback();
console.log(data);
dispatch({
type: EDIT_POST,
payload: data
})
})
}
}
//Action creator for submitting edited post
export function submitEditedPost(id, values, callback) {
console.log(values, 'values')
console.log(id, 'id')
const request = axios.put(`${API}/posts/${id}`, {values}, {headers});
return dispatch => {
return request.then((res) => {
callback();
console.log("response", res)
dispatch({
type:SUBMIT_POST,
payload: res
})
})
}
}
Suppose I have an API that return user detail:
/api/get_user/1
{
"status": 200,
"data": {
"username": "username1",
"email": "username#email.com"
}
}
And a "main function" like this:
function main (sources) {
const request$ = sources.ACTIONS
.filter(action => action.type === 'GET_USER_REQUEST')
.map(action => action.payload)
.map(payload => ({
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}))
const action$ = sources.HTTP
.select('GET_USER_REQUEST')
.flatten()
.map(response => response.data)
const sinks = {
HTTP: request$,
LOG: action$
}
return sinks
}
For testing the "ACTION" source, I can simply made an xstream observable
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const sources = { ACTION: actionStream$ }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
The question is. Is it possible to do the same thing (using plan xstream observable)
to test cycle-http driver without a helper from something like nock?
Or is there a better way to test something like this?
You can mock out the HTTP source like so:
test.cb('Test main function', t => {
const actionStream$ = xs.of({
type: 'GET_USER_REQUEST',
payload: { userId: 1 }
})
const response$ = xs.of({
data: {
status: 200,
data: {
username: "username1",
email: "username#email.com"
}
}
});
const HTTP = {
select (category) {
// if you have multiple categories you could return different streams depending on the category
return xs.of(response$);
}
}
const sources = { ACTION: actionStream$, HTTP }
const expectedResult = {
category: 'GET_USER_REQUEST',
url: `${BASE_URL}/api/get_user/${payload.userId}`,
method: 'GET'
}
main(sources).HTTP.addEventListener({
next: (data) => {
t.deepEqual(data, expectedResult)
},
error: (error) => {
t.fail(error)
},
complete: () => {
t.end()
}
})
})
Really, we should have a mockHTTPSource helper to make this a bit easier. I have opened an issue to that effect. https://github.com/cyclejs/cyclejs/issues/567
If you want to test that certain things happen at the correct time, you could use this pattern in conjunction with #cycle/time.
http://github.com/cyclejs/time
I'm trying to test a 'redux observable epic' but the test fail because not all actions are in store.getActions() the strange is the store.dispatch function runs.
Epic and actions
export const VERIFY_SESION = 'auth/VERIFY_SESION';
export const SET_POLICIES_ACCEPTED = 'auth/SET_POLICIES_ACCEPTED';
export const AUTHENTICATE = 'auth/AUTHENTICATE';
export function setPoliciesAccepted(wereAccepted: boolean) {
return {
wereAccepted,
type: SET_POLICIES_ACCEPTED,
};
}
export function verifySesion() {
return {
type: VERIFY_SESION,
};
}
export function authenticate(token) {
return {
token,
type: AUTHENTICATE,
};
}
export function verifySesionEpic(action$, store) {
return action$
.ofType(VERIFY_SESION)
.switchMap(async () => {
try {
store.dispatch(setBlockLoading(true));
const token = await AsyncStorage.getItem('token');
if (token !== null) {
store.dispatch(setBlockLoading(false));
return authenticate(token);
}
const policiesWereAccepted = await AsyncStorage.getItem('policiesWereAccepted');
store.dispatch(setBlockLoading(false));
return setPoliciesAccepted(policiesWereAccepted);
} catch (error) {
return setMessage(error.message);
}
});
}
test
describe('actions/auth', () => {
let store;
const asyncStorageGetStub = stub(AsyncStorage, 'getItem');
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
asyncStorageGetStub.restore();
});
it('Should call authenticate if token', () => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
});
});
Test result
1) "actions/auth Should call epic for verifySesion:
Error: Expected [ { type: 'auth/VERIFY_SESION' } ] to include { token: 'mitoken', type: 'auth/AUTHENTICATE' }"
Note
im sure that the conditional token !== null pass
I was to add a timeout before getAction because the 'AUTHENTICATE' actions is added after.
it('Should call authenticate if token', (done) => {
const token = 'mitoken';
asyncStorageGetStub.withArgs('token').returns(Promise.resolve(token));
store.dispatch(verifySesion());
setTimeout(() => {
expect(store.getActions()).toContain({ type: AUTHENTICATE, token });
done();
}, 1000);
});