Poll API every 2 seconds and update state until result turns successful/failure - React - async-await

I have an array of ids and I need to loop through the array and call an API with the id. The response from the API will have a Status field which ranges from [0,1,2,-1]. I want to call the API and update the state with the responses. I have a working code which has a Promise which gets resolved only if the Status is Finished or Failed. How do I go over about updating the state even while the Status is in Started and Queued.
This is my code.
import React from 'react';
import './style.css';
import { get, includes, keys, forEach, map } from 'lodash';
const Status = {
Queued: 0,
Started: 1,
Finished: 2,
Failed: -1,
};
/**
* Keeps getting data every 2.5 seconds
* If path is Status, keep calling the API every 2.5 seconds until Status is Finished or Failed, and then resolve
* #param url
* #param path
*/
/**
* The MAP where the list of timers are stored
*/
let dataFetchingTimerMap = {};
const clearDataFetchingTimer = (key) => {
/**
* Clear the timeout
*/
clearTimeout(dataFetchingTimerMap[key]);
/**
* Delete the key
*/
delete dataFetchingTimerMap[key];
};
const setDataFetchingTimer = (key, cb, delay) => {
/**
* Save the timeout with a key
*/
dataFetchingTimerMap[key] = window.setTimeout(() => {
/**
* Delete key when it executes
*/
clearDataFetchingTimer(key);
/**
* Execute the callback (loop function)
*/
cb();
}, delay);
};
export const getDataAtIntervals = (url, path) => {
const timerKey = `${url}_${path}`;
clearTimeout(+timerKey);
return new Promise((resolve, reject) => {
(async function loop() {
try {
const resultData = await fetch(url);
const result = await resultData.json();
if (
get(result, path) &&
includes(
[Status.Finished, Status.Failed, Status.FailedWithReturnFile],
get(result, path)
)
) {
/**
* Resolve with the data
*/
return resolve(result);
}
setDataFetchingTimer(timerKey, loop, 2500);
} catch (e) {
reject(e);
}
})();
});
};
/**
* Clear every timeout
*/
export const clearGetDataAtIntervals = () =>
forEach(keys(dataFetchingTimerMap), clearDataFetchingTimer);
export default function App() {
const [state, setState] = React.useState([]);
const handleClick = async () => {
const ids = [1, 2, 3];
const responses = await Promise.all(
map(
ids,
async (id) =>
await getDataAtIntervals(
`https://jsonplaceholder.typicode.com/todos/${id}`,
'completed'
)
)
);
setState(responses);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
Please advice. This is my Stackblitz link:
https://stackblitz.com/edit/react-dos3i3?file=src%2FApp.js,src%2Findex.js

Related

Cypress - Reload page until an element is located

I try to use cypress-wait-until for a simple case. https://github.com/NoriSte/cypress-wait-until
Visit a page
Check if an element is here
If not, reload the page until this element is found.
Once found, assert the element exists
Working code (cypress-wait-until not used)
before(() => {
cy.visit('http://localhost:8080/en/registration');
});
describe('Foo', () => {
it('should check that registration button is displayed', () => {
const selector = 'button[data-test=startRegistration-individual-button]';
cy.get(selector).should('exist');
});
});
Not working, timed out retrying
before(() => {
cy.visit('http://localhost:8080/en/registration');
});
describe('Foo', () => {
it('should check that registration button is displayed', () => {
const options = { timeout: 8000, interval: 4000 };
const selector = 'button[data-test=startRegistration-individual-button]';
cy.waitUntil(() => cy.reload().then(() => Cypress.$(selector).length), options);
cy.get(selector).should('exist');
});
});
Not working, see error below
before(() => {
cy.visit('http://localhost:8080/en/registration');
});
describe('Foo', () => {
it('should check that registration button is displayed', () => {
const options = { timeout: 8000, interval: 4000 };
const selector = 'button[data-test=startRegistration-individual-button]';
cy.waitUntil(() => {
cy.reload();
return Cypress.$(selector).length;
}, options);
cy.get(selector).should('exist');
});
For the two versions not working as soon as I remove cy.reload(), it starts to work.
Question
What can I do to make it work with a reload?
EDIT
This command I wrote works correctly.
Cypress.Commands.add('refreshUntil', (selector: string, opts?: { retries: number; waitAfterRefresh: number }) => {
const defaultOptions = {
retries: 10,
waitAfterRefresh: 2500,
};
const options = { ...defaultOptions, ...opts };
function check(selector: string): any {
if (Cypress.$(selector).length) { // Element is there
return true;
}
if (options.retries === 0) {
throw Error(`${selector} not found`);
}
options.retries -= 1;
cy.log(`Element ${selector} not found. Remaining attempts: ${options.retries}`);
cy.reload();
// Wait a some time for the server to respond
return cy.wait(options.waitAfterRefresh).then(() => check(selector));
}
check(parsedSelector);
});
I could see two potential difference with waitUntil from cypress-wait-until
Cypress.$(selector).length would be new on each try
There is a wait time after the reload before checking again if the element is there
EDIT 2
Here is the working solution using cypress-wait-until
cy.waitUntil(() => cy.reload().wait(2500).then(() => Cypress.$(selector).length), options);
Cypress rules apply inside cy.waitUntil() as well as outside so .wait(2500) would be bad practice.
It would be better to change your non-retying Cypress.$(selector).length into a proper Cypress retrying command. That way you get 4 seconds (default) retry but only wait as long as needed.
Particulary since cy.waitUntil() repeats n times, you are hard-waiting (wasting) a lot of seconds.
cy.waitUntil(() => { cy.reload(); cy.get(selector) }, options)
// NB retry period for `cy.get()` is > 2.5 seconds
Here is the working solution using cypress-wait-until
cy.waitUntil(() => cy.reload().wait(2500).then(() => Cypress.$(selector).length), options);
I ended up writing my own method (inspired from cypress-wait-until ) without the need to have a hard wait time
/**
* Run a check, and then refresh wait until an element is displayed.
* Retries for a specified amount of time.
*
* #function
* #param {function} firstCheckFunction - The function to run before checking if the element is displayed.
* #param {string|{ selector: string, type: string }} selector - The selector to search for. Can be a string or an object with selector and type properties.
* #param {WaitUntilOptions} [opts={timeout: 5000, interval: 500}] - The options object, with timeout and interval properties.
* #throws {Error} if the firstWaitFunction parameter is not a function.
* #throws {Error} if the specified element is not found after all retries.
* #example
* cy.refreshUntilDisplayed('#element-id', () => {...});
* cy.refreshUntilDisplayed({ selector: 'element-id', type: 'div' }, () => {...});
*/
Cypress.Commands.add('waitFirstRefreshUntilDisplayed', (firstCheckFunction, selector: string | { selector: string, type: string }, opts = {}) => {
if (!(firstCheckFunction instanceof Function)) {
throw new Error(`\`firstCheckFunction\` parameter should be a function. Found: ${firstCheckFunction}`);
}
let parsedSelector = '';
// Define the default options for the underlying `cy.wait` command
const defaultOptions = {
timeout: 5000,
interval: 500,
};
const options = { ...defaultOptions, ...opts };
// Calculate the number of retries to wait for the element to be displayed
let retries = Math.floor(options.timeout / options.interval);
const totalRetries = retries;
if (typeof selector === 'string') {
parsedSelector = selector;
}
if (typeof selector !== 'string' && selector.selector && selector.type) {
parsedSelector = `${selector.type}[data-test=${selector.selector}]`;
}
// Define the check function that will be called recursively until the element is displayed
function check(selector: string): boolean {
if (Cypress.$(selector).length) { // Element exists
return true;
}
if (retries < 1) {
throw Error(`${selector} not found`);
}
if (totalRetries !== retries) { // we don't reload first time
cy.log(`Element ${parsedSelector} not found. ${retries} left`);
cy.reload();
}
// Waits for the firstCheckFunction to return true,
// then pause for the time define in options.interval
// and call recursively the check function
cy.waitUntil(firstCheckFunction, options).then(() => { // We first for firstCheckFunction to be true
cy.wait(options.interval).then(() => { // Then we loop until the selector is displayed
retries -= 1;
return check(selector);
});
});
return false;
}
check(parsedSelector);
});

Unable to process events from azure EventHub after adding blob storage

The simple receiver function was working fine, but I was reading all the events again if the service restarts. To mitigate this added blob storage support for checkpoints.
After adding checkpointStore in the new EventHubConsumerClient it is not reading anything just logs "start event hub subscription"
const consumerClient = new EventHubConsumerClient(
consumerGroup,
connectionString,
eventHubName,
checkpointStore,
)
Am I missing something? Why is it not reaching to processEvents?
import { EventHubConsumerClient, CheckpointStore } from '#azure/event-hubs';
import { ContainerClient } from '#azure/storage-blob';
import { BlobCheckpointStore } from '#azure/eventhubs-checkpointstore-blob';
export async function startReceiverEventService() {
console.log(`Running receiveEvents sample`);
const connectionString = process.env.EVENTHUB_CONNECTION_STRING;
const eventHubName = process.env.EVENTHUB_NAME;
const consumerGroup = process.env.CONSUMER_GROUP_NAME;
const storageConnectionString = process.env.STORAGE_CONNECTION_STRING;
const containerName = process.env.STORAGE_CONTAINER_NAME;
const containerClient = new ContainerClient(
storageConnectionString,
containerName,
);
if (!(await containerClient.exists())) {
await containerClient.create();
}
/** Checkpoints for data consumed */
const checkpointStore: CheckpointStore = new BlobCheckpointStore(
containerClient,
);
/** Consumer connrection */
const consumerClient = new EventHubConsumerClient(
consumerGroup,
connectionString,
eventHubName,
checkpointStore,
);
console.log(' start event hub subscription ');
/** Subscribing the events */
const subscription = consumerClient.subscribe({
processEvents: async (events, context) => {
console.log('processing events -');
/**
* Note: It is possible for `events` to be an empty array.
* This can happen if there were no new events to receive
* in the `maxWaitTimeInSeconds`, which is defaulted to
* 60 seconds.
* The `maxWaitTimeInSeconds` can be changed by setting
* it in the `options` passed to `subscribe()`.
*/
for (const event of events) {
const data = JSON.stringify(event.body);
console.log(
`Received event: '${data}' from partition: '${context.partitionId}' and consumer group: '${context.consumerGroup}'`,
);
// TODO: have to do futher task here
}
try {
// save a checkpoint for the last event now that we've processed this batch.
await context.updateCheckpoint(events[events.length - 1]);
} catch (err) {
console.log(
`Error when checkpointing on partition ${context.partitionId}: `,
err,
);
throw err;
}
console.log(
`Successfully checkpointed event with sequence number: ${
events[events.length - 1].sequenceNumber
} from partition: '${context.partitionId}'`,
);
},
processError: async (err, context) => {
console.log(`Error : ${err}`);
},
});
}

Nodejs: axios request parallel way

I have a code which execute request sequentially
try {
const results = [];
for await (let tc of testCases) {
const { data } = await axios.post("URL",
{
command_line_arguments: "",
compiler_options: "",
redirect_stderr_to_stdout: true,
source_code: source_code,
language_id,
stdin: tc.input,
expected_output: tc.output,
}
);
results.push({ text: tc.text, input: tc.input, output: tc.output, testType: tc.testType, ...data });
}
it works, but it is very slow.
I am looking to request all of them in parallel way.
Note: I have tries Promise all,Promise.allsettled, and Axios.all. Some how it didnt worked.
My solution:
const runTestCases = async (testCases: ITestCase[], source_code, language_id) => {
try {
const requests = createRequests(testCases, source_code, language_id);
const result = await Promise.all(requests.map(async (response) => {
const { data }:any = await response;
return data;
}));
return result;
} catch (error) {
throw new BadRequestError(error?.message);
}
};
/**
* Create array of request to run test cases in parallel
* #param testCases
* #param advance
* #returns
*/
const createRequests = (testCases: ITestCase[], source_code: string, language_id: string) => {
const requests = testCases.map((tc) => {
return () => axios.post(process.env.JUDGE0_HOST,{
redirect_stderr_to_stdout: true,
source_code: source_code,
language_id,stdin: tc.input,
expected_output: tc.output
})})
return requests;
};
output:
[ undefined, undefined ]
Am I doing wrong ?
Thanks in advance!

Mock api in react redux-thunk project returning undefined

I am quite new in redux world and have not yet had a project structured the ducks way. I am trying to understand it and use it to make a mock api, since I don't have the backend ready yet. I am working with the legacy code, that I am trying to figure out. There is a folder called data, that has a duck and a backendApi file. Duck file looks like this.
data/duck.jsx
import { createSelector } from 'reselect';
import { createReduxApi } from './backendApi';
const getDataContext = state => state.default.dataContext;
const backendReduxApi = createBackendReduxApi(getDataContext);
// Action creators
export const makeRestApiRequest = endpointName => backendReduxApi .makeRequestActionCreator(endpointName);
export const resetRestApi = endpointName => backendReduxApi .makeResetActionCreator(endpointName);
// Reducers
export const dataReducer = backendReduxApi .createReducer();
// Selectors
const getRestApiState = endpointName => backendReduxApi .getEndpointState(endpointName);
export const getRestApiData = endpointName => createSelector([getRestApiState(endpointName)], apiState => apiState.data);
export const getRestApiMeta = endpointName => createSelector([getRestApiState(endpointName)], apiState => apiState.meta);
export const getRestApiError = endpointName => createSelector([getRestApiState(endpointName)], apiState => apiState.error);
export const getRestApiStarted = endpointName => createSelector([getRestApiState(endpointName)], apiState => apiState.started);
export const getRestApiFinished = endpointName => createSelector([getRestApiState(endpointName)], apiState => apiState.finished);
The backendApi.jsx file looks like this:
data/backendApi.jsx
import ReduxRestApi from './rest/ReduxRestApi';
export const BackendApi = { // NOSONAR
LANGUAGE_FILE: 'languageFile',
EMPLOYEE: 'employee',
};
const backendReduxApiBuilder = ReduxRestApi.build()
/* /api */
/* /api/employee */
.withGet('/myproject/api/employee', BackendApi.EMPLOYEE)
/* /language*/
.withGet('/myproject/language/nb_NO.json', BackendApi.LANGUAGE_FILE)
export const createBackendReduxApi = restApiSelector => backendReduxApiBuilder
.withRestApiSelector(restApiSelector)
.create();
Then in the data/rest folder I have 4 files: ReduxRestApi, restConfig, RestDuck and restMethods.
data/rest/ReduxRestApi.jsx
import { combineReducers } from 'redux';
import { get, post, postAndOpenBlob } from './restMethods';
import RestDuck from './RestDuck';
class ReduxRestApi {
constructor(endpoints, getRestApiState) {
this.createReducer = this.createReducer.bind(this);
this.getEndpoint = this.getEndpoint.bind(this);
this.makeRequestActionCreator = this.makeRequestActionCreator.bind(this);
this.makeResetActionCreator = this.makeResetActionCreator.bind(this);
this.getEndpointState = this.getEndpointState.bind(this);
this.ducks = endpoints.map(({ name, path, restMethod }) => new RestDuck(name, path, restMethod, getRestApiState));
}
createReducer() {
const reducers = this.ducks
.map(duck => ({ [duck.name]: duck.reducer }))
.reduce((a, b) => ({ ...a, ...b }), {});
return combineReducers(reducers);
}
getEndpoint(endpointName) {
return this.ducks.find(duck => duck.name === endpointName)
|| { actionCreators: {} };
}
makeRequestActionCreator(endpointName) {
return this.getEndpoint(endpointName).actionCreators.execRequest;
}
makeResetActionCreator(endpointName) {
return this.getEndpoint(endpointName).actionCreators.reset;
}
getEndpointState(endpointName) {
return this.getEndpoint(endpointName).stateSelector;
}
static build() {
class RestApiBuilder {
constructor() {
this.withGet = this.withGet.bind(this);
this.withPost = this.withPost.bind(this);
this.withPostAndOpenBlob = this.withPostAndOpenBlob.bind(this);
this.withRestApiSelector = this.withRestApiSelector.bind(this);
this.endpoints = [];
}
withGet(path, name) {
this.endpoints.push({ path, name, restMethod: get });
return this;
}
withPost(path, name) {
this.endpoints.push({ path, name, restMethod: post });
return this;
}
withPostAndOpenBlob(path, name) {
this.endpoints.push({ path, name, restMethod: postAndOpenBlob });
return this;
}
withRestApiSelector(restApiSelector) {
this.restApiSelector = restApiSelector;
return this;
}
create() {
return new ReduxRestApi(
this.endpoints,
this.restApiSelector
);
}
}
return new RestApiBuilder();
}
}
export default ReduxRestApi;
restConfig.jsx
import axios from 'axios';
import { removeErrorMessage, showErrorMessage } from '../../app/duck';
import { is401Error, isHandledError } from '../../app/ErrorTypes';
const isDevelopment = process.env.NODE_ENV === 'development';
const configureRequestInterceptors = (store) => {
const onRequestAccepted = (config) => {
store.dispatch(removeErrorMessage());
return config;
};
const onRequestRejected = error => Promise.reject(error);
axios.interceptors.request.use(onRequestAccepted, onRequestRejected);
};
const configureResponseInterceptors = (store) => {
const onSuccessResponse = response => response;
const onErrorResponse = (error) => {
if (is401Error(error) && !isDevelopment) {
window.location.reload();
}
if (!isHandledError(error)) {
store.dispatch(showErrorMessage(error));
}
return Promise.reject(error);
};
axios.interceptors.response.use(onSuccessResponse, onErrorResponse);
};
const configureRestInterceptors = (store) => {
configureRequestInterceptors(store);
configureResponseInterceptors(store);
};
export default configureRestInterceptors;
data/rest/RestDuck.jsx
import { createSelector } from 'reselect';
import { get, getBlob, post, postAndOpenBlob, postBlob } from './restMethods';
/**
* getMethodName
* Helper function that maps given AJAX-method to a name
*
* Ex. getMethodName(getBlob) -> 'GET'
*/
const getMethodName = (restMethod) => {
switch (restMethod) {
case get:
case getBlob:
return 'GET';
case post:
case postBlob:
case postAndOpenBlob:
return 'POST';
default:
return '';
}
};
/**
* createRequestActionType
* Helper function to generate actionType for actions related to AJAX calls
*
* Ex: createRequestActionType('fetchEmployee', 'ERROR', get, '/myproject/api/employee') -> '##REST/fetchEmployee GET /myproject/api/employeeERROR'
*/
const createRequestActionType = (name, qualifier, restMethod = '', path = '') => [`##REST/${name}`, getMethodName(restMethod), path, qualifier]
.filter(s => s !== '')
.join(' ');
/**
* createRequestActionTypes
* Helper function to generate ActionTypes for a given AJAX method and resource.
*
* Ex. createRequestActionType(fetchEmployee, get, '/myproject/api/employee') -> {
* reset: '##REST GET /myproject/api/employee RESET',
* requestStarted: '##REST GET /myproject/api/employee STARTED',
* requestError: '##REST GET /myproject/api/employee ERROR',
* requestFinished: '##REST GET /myproject/api/employee FINISHED',
* }
*/
const createRequestActionTypes = (name, restMethod, path) => ({
reset: createRequestActionType(name, 'RESET'),
requestStarted: createRequestActionType(name, 'STARTED', restMethod, path),
requestError: createRequestActionType(name, 'ERROR', restMethod, path),
requestFinished: createRequestActionType(name, 'FINISHED', restMethod, path)
});
/**
* createRequestThunk
* Helper function that generates a thunk that performs an AJAX call specified by 'restMethod' and 'restEndpoint'
*
* When the thunk is running, the action 'requestStarted' will be dispatched immediately.
* Then, it performs the AJAX call that returns a promise.
* If the call goes well, the action 'requestFinished' will be dispatched with data from the call.
* If the call fails, the action 'requestError' is dispatched with the contents of the error.
*/
const createRequestThunk = (restMethod, restEndpoint, requestStarted, requestFinished, requestError) => (
(params, options = {}) => (dispatch) => {
dispatch(requestStarted(params, options));
return restMethod(restEndpoint, params)
.catch((error) => {
const data = error.response && error.response.data ? error.response.data : error;
dispatch(requestError(data));
return Promise.reject(error);
})
.then((response) => {
dispatch(requestFinished(response.data));
return response;
});
}
);
/**
* createRequestActionCreators
* Helper function that creates action creators 'requestStarted', 'requestFinished' and 'requestError',
* #see createRequestThunkCreator
*/
const createRequestActionCreators = (restMethod, restEndpoint, actionTypes) => {
const reset = () => ({ type: actionTypes.reset });
const requestStarted = (params, options = {}) => ({ type: actionTypes.requestStarted, payload: { params, timestamp: Date.now() }, meta: { options } });
const requestFinished = data => ({ type: actionTypes.requestFinished, payload: data });
const requestError = error => ({ type: actionTypes.requestError, payload: error });
const execRequest = createRequestThunk(restMethod, restEndpoint, requestStarted, requestFinished, requestError);
return {
reset, requestStarted, requestFinished, requestError, execRequest
};
};
/**
* createRequestReducer
*
* Helper function that creates a reducer for an AJAX call.
* Reducer alters the state of the actions with the name defined by
* actionTypes.requestStarted
* actionTypes.requestFinished
* actionTypes.requestError
*/
const createRequestReducer = (restMethod, resourceName, actionTypes) => {
const initialState = {
data: undefined,
meta: undefined,
error: undefined,
started: false,
finished: false
};
return (state = initialState, action = {}) => {
switch (action.type) {
case actionTypes.requestStarted:
return {
...initialState,
data: action.meta.options.keepData ? state.data : initialState.data,
started: true,
meta: action.payload
};
case actionTypes.requestFinished:
return {
...state,
started: false,
finished: true,
data: action.payload
};
case actionTypes.requestError:
return {
...state,
started: false,
error: action.payload
};
case actionTypes.reset:
return {
...initialState
};
default:
return state;
}
};
};
/**
* RestDuck
* Class that offers action types, action creators, reducers and selectors for an AJAX call.
* #see createRequestActionTypes
* #see createRequestActionCreators
* #see createRequestReducer
*
* Ex.
* const getEmployeeDuck = new RestDuck(execGetRequest, 'employee', GET_EMPLOYEE_SERVER_URL);
* // Action creators
* export const fetchEmployee = getEmployeeDuck.actionCreators.execRequest;
* // Reducer
* export const dataReducer = combineReducers(
* ...,
* getEmployeeDuck.reducer,
* }
* // Selectors
* export const getDataContext = state => state.default.dataContext;
* export const getEmployeeData = getEmployeeDuck.selectors.getRequestData(getDataContext);
* export const getEmployeeStarted = getEmployeeDuck.selectors.getRequestStarted(getDataContext);
* ...
*/
class RestDuck {
constructor(name, path, restMethod, getApiContext) {
this.restMethod = restMethod;
this.name = name;
this.path = path;
this.getApiContext = getApiContext;
this.$$duck = {}; // for class internal use
}
get actionTypes() {
if (!this.$$duck.actionTypes) {
this.$$duck.actionTypes = createRequestActionTypes(this.name, this.restMethod, this.path);
}
return this.$$duck.actionTypes;
}
get actionCreators() {
if (!this.$$duck.actionCreators) {
this.$$duck.actionCreators = createRequestActionCreators(this.restMethod, this.path, this.actionTypes);
}
return this.$$duck.actionCreators;
}
get reducer() {
if (!this.$$duck.reducer) {
this.$$duck.reducer = createRequestReducer(this.restMethod, this.name, this.actionTypes);
}
return this.$$duck.reducer;
}
get stateSelector() {
return createSelector([this.getApiContext], restApiContext => restApiContext[this.name]);
}
}
export default RestDuck;
data/rest/restMethods.jsx
import axios, { CancelToken } from 'axios';
const openPreview = (data) => {
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(data);
} else {
window.open(URL.createObjectURL(data));
}
};
const cancellable = (config) => {
let cancel;
const request = axios({
...config,
cancelToken: new CancelToken((c) => { cancel = c; })
});
request.cancel = cancel;
return request.catch(error => (axios.isCancel(error) ? Promise.reject(new Error(null)) : Promise.reject(error)));
};
const defaultHeaders = {
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
Expires: 0
};
const defaultPostHeaders = {
'Content-Type': 'application/json'
};
export const get = (url, params, responseType = 'json') => cancellable({
url,
params,
responseType,
method: 'get',
headers: {
...defaultHeaders
}
});
export const post = (url, data, responseType = 'json') => cancellable({
url,
responseType,
data: JSON.stringify(data),
method: 'post',
headers: {
...defaultHeaders,
...defaultPostHeaders
},
cache: false
});
export const getBlob = (url, params) => get(url, params, 'blob');
export const postBlob = (url, data) => post(url, data, 'blob');
export const postAndOpenBlob = (url, data) => postBlob(url, data)
.then((response) => {
openPreview(response.data);
return {
...response,
data: 'blob opened as preview' // Don't waste memory by storing blob in state
};
});
I am not sure where to place and how to do mock api calls in this structure. I was thinking of making a mock api similiar to this one, where I would mimick the ajax calls and store them in the redux, but just not sure how to do this in this kind of setup?
I have tried with making the mockApi folder and instead of using the restMethods, to use the file where I would write promises that would resolve the mockData. This is my attempt:
mockRestMethods
const employee = {
name: 'Joe Doe'
}
const data = {
employee
};
export const get = item => new Promise((resolve) => {
setTimeout(() => {
resolve({ data: data[item] });
}, 1000);
});
But, if I inspect what is returned as the response.data inside the createRequestThunk function in the RestDuck file I get data: undefined there. Why is that, what am I doing wrong?
I may well have this wrong, but it seems like you are replacing
export const get = (url, params, responseType = 'json') => cancellable({
with export const get = item => new Promise((resolve) => { which has a different API.
Regardless, have you tried logging the value of item in the mock get function. I'm guessing it isn't "employee" which is the only property in data.
Yes, that was my goal, to replace the call that was pointing to the backend API, with the call where I would return the mock data. I have tried to log the value of the item, but I get undefined
ok, so there's an awful lot of abstraction going on there. Id suggest starting by replacing get in data/rest/restMethods.jsx directly with a version that returns a promise, get it working, and then break it out. That way you're not dealing with too many unknowns at once.
I had done similar using redux-saga. After debugging, I had found that there must be data property as root key. Here's how you should do:
const employee = {
data: { // root key of employee
items: [
{ name: 'Bhojendra' },
{ name: 'Rauniyar' }
]
}
}
// and no need to use setTimeout, we're just resolving some constant data
export const getItems = item => new Promise(resolve => resolve(employee))
Now, I hope you know why data is undefined with your code.
Still not clear?
Response looks for the data property. That's it.

Redux action ajax result not dispatched to reducer

I just get to experiment with Redux and I know that middleware is essential to make ajax calls. I've installed redux-thunk and axios package separately and tried to hook my result as a state and render the ajax result to my component. However my browser console displays an error and my reducer couldn't grab the payload.
The error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
This is part of my code and how the middleware is hooked up:
//after imports
const logger = createLogger({
level: 'info',
collapsed: true,
});
const router = routerMiddleware(hashHistory);
const enhancer = compose(
applyMiddleware(thunk, router, logger),
DevTools.instrument(),
persistState(
window.location.href.match(
/[?&]debug_session=([^&]+)\b/
)
)
// store config here...
my action:
import axios from 'axios';
export const SAVE_SETTINGS = 'SAVE_SETTINGS';
const url = 'https://hidden.map.geturl/?with=params';
const request = axios.get(url);
export function saveSettings(form = {inputFrom: null, inputTo: null}) {
return (dispatch) => {
dispatch(request
.then((response) => {
const alternatives = response.data.alternatives;
var routes = [];
for (const alt of alternatives) {
const routeName = alt.response.routeName;
const r = alt.response.results;
var totalTime = 0;
var totalDistance = 0;
var hasToll = false;
// I have some logic to loop through r and reduce to 3 variables
routes.push({
totalTime: totalTime / 60,
totalDistance: totalDistance / 1000,
hasToll: hasToll
});
}
dispatch({
type: SAVE_SETTINGS,
payload: { form: form, routes: routes }
});
})
);
}
}
reducer:
import { SAVE_SETTINGS } from '../actions/configure';
const initialState = { form: {configured: false, inputFrom: null, inputTo: null}, routes: [] };
export default function configure(state = initialState, action) {
switch (action.type) {
case SAVE_SETTINGS:
return state;
default:
return state;
}
}
you can see the state routes has size of 0 but the action payload has array of 3.
Really appreciate any help, thanks.
It looks like you have an unnecessary dispatch in your action, and your request doesn't look to be instantiated in the correct place. I believe your action should be:
export function saveSettings(form = { inputFrom: null, inputTo: null }) {
return (dispatch) => {
axios.get(url).then((response) => {
...
dispatch({
type: SAVE_SETTINGS,
payload: { form: form, routes: routes }
});
});
};
}

Resources