I have the following redux-sagas:
import { all, call, fork, put, takeEvery } from "redux-saga/effects";
import { catalogs } from 'backend/Catalogs';
import {
ON_FETCH_CATALOGS,
ON_CRUD_POPULATE_LIST,
ON_CRUD_FETCH_NEXT_PAGE,
ON_CRUD_FETCH_PREVIOUS_PAGE,
ON_CRUD_FETCH_PAGE,
ON_CRUD_CREATE,
ON_CRUD_DELETE,
ON_CRUD_REFRESH_PAGE,
ON_CATALOG_POPULATE_OPTIONS,
ON_CRUD_UPDATE,
ON_CRUD_FETCH_LIST,
ON_CRUD_FETCH_RECORD,
GET_TAGS,
ON_FETCH_AUTOMATS,
} from "constants/ActionTypes";
import {
fetchCatalogsSuccess,
showCatalogsMessage,
crudPopulateListSuccess,
crudFetchNextPageSuccess,
crudFetchPreviousPageSuccess,
crudFetchPageSuccess,
crudRefreshPage,
crudRefreshPageSuccess,
catalogPopulateOptionsSuccess,
crudFetchListSuccess,
crudFetchRecordSuccess,
ListTagsSuccess,
fetchAutomatsSuccess,
} from 'actions/Catalogs';
import { ALERT_ERROR ,ALERT_SUCCESS } from 'attractora/AttrAlert';
...
/// fetchAutomats
const fetchAutomatsRequest = async () => {
return await catalogs.fetchAutomats()
.then (automats => automats)
.catch (error => error)
}
function* fetchAutomatsFromRequest ({payload}) {
try {
const automats = yield call(fetchAutomatsRequest);
if (automats.message) {
yield put(showCatalogsMessage(ALERT_ERROR, automats.message));
} else {
yield put(fetchAutomatsSuccess(automats));
}
} catch (error) {
yield put(showCatalogsMessage(ALERT_ERROR, error));
}
}
export function* fetchAutomats() {
yield takeEvery(ON_FETCH_AUTOMATS, fetchAutomatsFromRequest);
}
/// rootSaga
export default function* rootSaga() {
yield all([
fork(fetchCatalogsList),
fork(crudPopulateList),
fork(crudFetchNextPage),
fork(crudFetchPreviousPage),
fork(crudFetchPage),
fork(crudCreateRecord),
fork(crudUpdateRecord),
fork(crudDeleteRecord),
fork(crudSagaRefreshPage),
fork(catalogPopulateOptions),
fork(crudFetchList),
fork(crudFetchRecord),
fork(crudPopulateListTags),
fork(fetchAutomats),
]);
}
And this backend.js file:
export const fetchAutomats = () => {
var requestString = backendServer + 'api/general/automats_for_form/'
axios.get(requestString, getAuthHeader())
.then((Response) => {
return { automats: Response.data, message: null };
})
.catch((Error) => {
return getErrorMessage(Error);
})
}
export const catalogs = {
getCatalogs: getCatalogs,
populateList: populateList,
fetchNextPage: fetchNextPage,
fetchPreviousPage: fetchPreviousPage,
fetchPage: fetchPage,
createRecord: createRecord,
deleteRecord: deleteRecord,
refreshPage: refreshPage,
getList: getList,
updateRecord: updateRecord,
fetchList: fetchList,
fetchRecord: fetchRecord,
populateListTags: populateListTags,
fetchAutomats: fetchAutomats,
searchRepositoryByName,
};
For some reason, line return await catalogs.fetchAutomats() rises the following error: TypeError: Cannot read property 'then' of undefined at fetchAutomatsRequest.
I cannot find where my error lies.
The issue is within the fetchAutomats method, you need to return the promise from the method:
export const fetchAutomats = () => {
var requestString = backendServer + 'api/general/automats_for_form/'
return axios.get(requestString, getAuthHeader())
.then((Response) => {
return { automats: Response.data, message: null };
})
.catch((Error) => {
return getErrorMessage(Error);
})
}
Related
I have the following code:
//agent.js
import axios from 'axios';
axios.defaults.baseURL = 'https://localhost:5001/api';
const requests = {
createUser: (payload) => {
axios.post('/users/create', payload);
},
getUsers: () => {
axios.get('/users').then((r) => {
console.log(r.data); //outputs the json response
return r.data;
});
}
};
const agent = {
requests
};
export default agent;
//reactComponent.js
import agent from './agent';
function Userlist() {
const users = agent.requests.getUsers();
console.log(users); //outputs undefined
}
What am I doing wrong as I get an undefined when making the request from my reactComponent.js.
Because you are not returning anything in your getUsers function.
getUsers: () => {
axios.get('/users').then((r) => {
console.log(r.data); //outputs the json response
return r.data;
});
}
Remove the function bracket and it should work,
getUsers: () =>
axios.get('/users').then((r) => {
console.log(r.data); //outputs the json response
return r.data;
});
I have some component with a code like this:
const startLogin = (code) => {
dispatch(login({ code }));
const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
setPublicKey(publicKeyFromLocalST);
// etc
When I dispatch the saga login it will store some data in localStorage.
I need to execute the 3rd line (setPublicKey) after that data be actually indeed in localStorage.
How can "await" for dispatch(login({ code })); to be completed before setPublicKey?
Two options:
Execute the setPublicKey function inside the worker saga, you can control the workflow in the worker saga easily with yield.
function* login(action) {
const response = yield call(apiCall);
if (response.error) {
yield put({ type: actionType.LOGIN_FAIL });
} else {
yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
const publicKeyFromLocalST = window.localStorage.getItem('push_public_key');
setPublicKey(publicKeyFromLocalST);
}
}
Promisify the dispatch(login({code})), you should create a helper function like this:
const loginAsyncCreator = (dispatch) => (payload) => {
return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
};
You need to pass the resolve/reject to worker saga via action.meta, then you can decide when to resolve or reject the promise. Then, you can use async/await in your event handler. See below example:
import { call, put, takeLatest } from 'redux-saga/effects';
import { createStoreWithSaga } from '../../utils';
const actionType = {
LOGIN: 'LOGIN',
LOGIN_FAIL: 'LOGIN_FAIL',
LOGIN_SUCCESS: 'LOGIN_SUCCESS',
};
function apiCall() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ error: null, data: 'login success' });
}, 2000);
});
}
function* login(action) {
console.log('action: ', action);
const {
meta: { resolve, reject },
} = action;
const response = yield call(apiCall);
console.log('response: ', response);
if (response.error) {
yield put({ type: actionType.LOGIN_FAIL });
yield call(reject, response.error);
} else {
yield put({ type: actionType.LOGIN_SUCCESS, data: response.data });
yield call(resolve, response.data);
}
}
function* watchLogin() {
yield takeLatest(actionType.LOGIN, login);
}
const store = createStoreWithSaga(watchLogin);
function loginCreator(payload, meta) {
return {
type: actionType.LOGIN,
payload,
meta,
};
}
const loginAsyncCreator = (dispatch) => (payload) => {
return new Promise((resolve, reject) => dispatch(loginCreator(payload, { resolve, reject })));
};
const loginAsync = loginAsyncCreator(store.dispatch);
async function startLogin() {
await loginAsync({ code: '1' });
console.log('setPublicKey');
}
startLogin();
The logs:
action: {
type: 'LOGIN',
payload: { code: '1' },
meta: { resolve: [Function (anonymous)], reject: [Function (anonymous)] }
}
response: { error: null, data: 'login success' }
setPublicKey
I have the following pipeline in my React App
saga.js :
function* handleGetTrack(action: ReturnType<typeof selectTrack>) {
try {
const getTrackResponse = yield httpGetTrack(action.payload)
yield console.log(getTrackResponse)
getTrackResponse.status === 200
? yield put(trackSelected(getTrackResponse.data))
: yield put(selectTrackError('Track Not Found (Get)'))
} catch (err) {
if (err instanceof Error && err.stack) {
yield put(selectTrackError(err.stack))
} else {
yield put(selectTrackError('An unknown error occured.'))
}
}
}
Api.js
export function httpGetTrack(trackId) {
return customAxios.get(`${URL}/tracks/${trackId}`)
}
customAxios.js :
export const customAxios = axios.create({
baseURL: process.env.REACT_APP_API_ENDPOINT,
timeout: 10000,
})
customAxios.interceptors.response.use(
function(response) {
return response
},
function(error) {
const errorResponse = error.response
if (isTokenExpiredError(errorResponse)) {
return resetTokenAndReattemptRequest(error)
}
return error.response
}
)
Like this everything works fine, In fact in my saga.js I can make the console.log(getTrackResponse) print the error well,
However in the Axios documentation it says tu use return Promise.reject(error) instead of return error.response
Why is that ?? Am i doing well or wrong ??
Have you tried using the call effect? For example:
import { call } from 'redux-saga/effects';
...
const getTrackResponse = yield call(httpGetTrack, action.payload);
...
When you define your interceptor you should also follow the Axios docs
function(error) {
const errorResponse = error.response
...
return Promise.reject(error.response);
}
import Api from './path/to/api'
import { call, put } from 'redux-saga/effects'
function fetchProductsApi() {
return Api.fetch('/products')
.then(response => ({ response }))
.catch(error => ({ error }))
}
function* fetchProducts() {
const { response, error } = yield call(fetchProductsApi)
if (response)
yield put({ type: 'PRODUCTS_RECEIVED', products: response })
else
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
Documentation
Here is an answer for a similar problem
So I have a saga which shows fetches some data to show in a table.
Action Creators are as follows
export const fetchInstanceDataSetAssocSuccess = (records) => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_SUCCESS,
records: records
}
}
export const fetchInstanceDataSetAssocFailed = (error) => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_FAILED,
error: error
}
}
export const fetchInstanceDataSetAssocStart = () => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_START
}
}
export const fetchInstanceDataSetAssoc = () => {
return {
type: actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_INITIATE
}
}
My saga is as follows
function * fetchInstanceDataSetAssocSaga (action) {
yield put(instanceDataSetAssocActions.fetchInstanceDataSetAssocStart())
const useMockData = yield constants.USE_MOCK_DATA
if (useMockData) {
yield delay(constants.MOCK_DELAY_SECONDS * 1000)
}
try {
const res = (useMockData)
? (yield constants.INSTANCE_DATASET_ASSOC)
: (yield call(request, {url:
API_URLS.INSTANCE_DATASET_ASSOC_API_ENDPOINT, method: 'GET'}))
yield put(instanceDataSetAssocActions.fetchInstanceDataSetAssocSuccess(res.data))
} catch (error) {
yield
put(instanceDataSetAssocActions.fetchInstanceDataSetAssocFailed(error))
}
}
Action to watch over the Saga is as follows
export function * watchInstanceDataSetAssocSaga () {
yield takeEvery(actionTypes.FETCH_INSTANCE_DATASETS_ASSOC_INITIATE,
fetchInstanceDataSetAssocSaga)
}
Test Cases are as follows
describe('load instance dataset assoc table', () => {
test('update state with instance-dataset records for landing page',() => {
const finalState = {
records: constants.INSTANCE_DATASET_ASSOC.data,
loading: false,
error: false
}
const requestParam = {url: API_URLS.INSTANCE_DATASET_ASSOC_API_ENDPOINT, method: 'GET'}
return expectSaga(watchInstanceDataSetAssocSaga)
.provide([[call(request,requestParam),constants.INSTANCE_DATASET_ASSOC]])
.withReducer(instanceDataSetAssoc)
.put(instanceDataSetAssocActions.fetchInstanceDataSetAssocStart())
.put(instanceDataSetAssocActions.fetchInstanceDataSetAssocSuccess(constants.INSTANCE_DATASET_ASSOC.data))
.dispatch(instanceDataSetAssocActions.fetchInstanceDataSetAssoc())
.hasFinalState(finalState)
.silentRun()
})
})
I get the following error for this.
SagaTestError:
put expectation unmet:
at new SagaTestError (node_modules/redux-saga-test-plan/lib/shared/SagaTestError.js:17:57)
at node_modules/redux-saga-test-plan/lib/expectSaga/expectations.js:63:13
at node_modules/redux-saga-test-plan/lib/expectSaga/index.js:572:7
at Array.forEach (<anonymous>)
at checkExpectations (node_modules/redux-saga-test-plan/lib/expectSaga/index.js:571:18)
I am following the docs correctly but still getting the above error.
Maybe its late, but i found an answer, maybe it will help you
This mistake may occure because of library timeout try to turn off the timeout with .run(false)
original link https://github.com/jfairbank/redux-saga-test-plan/issues/54
action.js
export function getLoginStatus() {
return async(dispatch) => {
let token = await getOAuthToken();
let success = await verifyToken(token);
if (success == true) {
dispatch(loginStatus(success));
} else {
console.log("Success: False");
console.log("Token mismatch");
}
return success;
}
}
component.js
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
});
}
However, when I write component.js code with async/await like below I get this error:
Possible Unhandled Promise Rejection (id: 0): undefined is not a function (evaluating 'this.props.dispatch(splashAction.getLoginStatus())')
component.js
async componentDidMount() {
let success = await this.props.dispatch(splashAction.getLoginStatus());
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
}
How do I await a getLoginStatus() and then execute the rest of the statements?
Everything works quite well when using .then(). I doubt something is missing in my async/await implementation. trying to figure that out.
The Promise approach
export default function createUser(params) {
const request = axios.post('http://www...', params);
return (dispatch) => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
request.then(success => onSuccess, error => onError);
};
}
The async/await approach
export default function createUser(params) {
return async dispatch => {
function onSuccess(success) {
dispatch({ type: CREATE_USER, payload: success });
return success;
}
function onError(error) {
dispatch({ type: ERROR_GENERATED, error });
return error;
}
try {
const success = await axios.post('http://www...', params);
return onSuccess(success);
} catch (error) {
return onError(error);
}
}
}
Referenced from the Medium post explaining Redux with async/await: https://medium.com/#kkomaz/react-to-async-await-553c43f243e2
Remixing Aspen's answer.
import axios from 'axios'
import * as types from './types'
export function fetchUsers () {
return async dispatch => {
try {
const users = await axios
.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => res.data)
dispatch({
type: types.FETCH_USERS,
payload: users,
})
} catch (err) {
dispatch({
type: types.UPDATE_ERRORS,
payload: [
{
code: 735,
message: err.message,
},
],
})
}
}
}
import * as types from '../actions/types'
const initialErrorsState = []
export default (state = initialErrorsState, { type, payload }) => {
switch (type) {
case types.UPDATE_ERRORS:
return payload.map(error => {
return {
code: error.code,
message: error.message,
}
})
default:
return state
}
}
This will allow you to specify an array of errors unique to an action.
Another remix for async await redux/thunk. I just find this a bit more maintainable and readable when coding a Thunk (a function that wraps an expression to delay its evaluation ~ redux-thunk )
actions.js
import axios from 'axios'
export const FETCHING_DATA = 'FETCHING_DATA'
export const SET_SOME_DATA = 'SET_SOME_DATA'
export const myAction = url => {
return dispatch => {
dispatch({
type: FETCHING_DATA,
fetching: true
})
getSomeAsyncData(dispatch, url)
}
}
async function getSomeAsyncData(dispatch, url) {
try {
const data = await axios.get(url).then(res => res.data)
dispatch({
type: SET_SOME_DATA,
data: data
})
} catch (err) {
dispatch({
type: SET_SOME_DATA,
data: null
})
}
dispatch({
type: FETCHING_DATA,
fetching: false
})
}
reducers.js
import { FETCHING_DATA, SET_SOME_DATA } from './actions'
export const fetching = (state = null, action) => {
switch (action.type) {
case FETCHING_DATA:
return action.fetching
default:
return state
}
}
export const data = (state = null, action) => {
switch (action.type) {
case SET_SOME_DATA:
return action.data
default:
return state
}
}
Possible Unhandled Promise Rejection
Seems like you're missing the .catch(error => {}); on your promise. Try this:
componentDidMount() {
this.props.dispatch(splashAction.getLoginStatus())
.then((success) => {
if (success == true) {
Actions.counter()
} else {
console.log("Login not successfull");
}
})
.catch(err => {
console.error(err.getMessage());
}) ;
}
use dispatch(this.props.splashAction.getLoginStatus()) instead this.props.dispatch(splashAction.getLoginStatus())