react-redux-dispatch-async
Version:
hooks & redux middleware to be able to wait async actions with fixed defined suffixes
230 lines (197 loc) • 6.21 kB
JavaScript
var react = require('react');
var reactRedux = require('react-redux');
var createId = function createId() {
return '_' + Math.random().toString(36).substr(2, 9);
};
var listeners = {};
var addActionListener = function addActionListener(newListener) {
var listenerId = createId();
listeners[listenerId] = newListener;
return function () {
return delete listeners[listenerId];
};
};
var ConfigMiddleware = {
initialized: false,
suffixes: {
request: 'REQUESTED',
success: 'SUCCEEDED',
failure: 'FAILED',
cancel: 'CANCELED'
}
};
var isAsyncAction = function isAsyncAction(config, action) {
return action.type.endsWith(config.request) || action.type.endsWith(config.success) || action.type.endsWith(config.failure) || action.type.endsWith(config.cancel);
};
var createDispatchAsyncMiddleware = function createDispatchAsyncMiddleware(config) {
return function () {
return function (next) {
return function (action) {
try {
if (config && !ConfigMiddleware.initialized) {
ConfigMiddleware.suffixes = config;
ConfigMiddleware.initialized = true;
}
if (isAsyncAction(ConfigMiddleware.suffixes, action) && Object.keys(listeners).length > 0) {
for (var _i = 0, _Object$values = Object.values(listeners); _i < _Object$values.length; _i++) {
var listener = _Object$values[_i];
listener(action);
}
}
} catch (error) {
console.error(error);
}
return next(action);
};
};
};
};
function dispatchAsync(dispatch, action) {
return new Promise(function (resolve) {
var actionNameBase = action.type.replace("_" + ConfigMiddleware.suffixes.request, '');
var unsubscribe = addActionListener(function (resultAction) {
if (resultAction.type === actionNameBase + "_" + ConfigMiddleware.suffixes.success) {
resolve({
success: true,
result: resultAction.payload
});
unsubscribe();
} else if (resultAction.type === actionNameBase + "_" + ConfigMiddleware.suffixes.failure) {
var error = resultAction.payload instanceof Error ? resultAction.payload : new Error("Action failure: " + actionNameBase);
resolve({
success: false,
error: error
});
unsubscribe();
} else if (resultAction.type === actionNameBase + "_" + ConfigMiddleware.suffixes.cancel) {
resolve({
success: false,
error: new Error('canceled'),
canceled: true
});
unsubscribe();
}
});
dispatch(action);
});
}
function useCompatDispatchAsync(action) {
var dispatch = reactRedux.useDispatch();
if (action) {
return function () {
return dispatchAsync(dispatch, action);
};
}
return function (action) {
return dispatchAsync(dispatch, action);
};
} // the hook
function useDispatchAsync(actionFunction, deps, options) {
if (deps === void 0) {
deps = [];
}
if (options === void 0) {
options = {
timeoutInMilliseconds: 15000
};
}
var dispatch = reactRedux.useDispatch(); // 👉 Better flow with informative & useful return
var _useState = react.useState(undefined),
result = _useState[0],
setResult = _useState[1];
var _useState2 = react.useState(undefined),
error = _useState2[0],
setError = _useState2[1];
var _useState3 = react.useState(false),
isTimeout = _useState3[0],
setIsTimeout = _useState3[1];
var _useState4 = react.useState(false),
isCancel = _useState4[0],
setIsCancel = _useState4[1]; // 👉 race condition to get last update
// https://sebastienlorber.com/handling-api-request-race-conditions-in-react
// A ref to store the last issued pending request
var lastPromise = react.useRef();
react.useEffect(function () {
var actionPromise = dispatchAsync(dispatch, actionFunction.apply(void 0, deps));
var timeoutPromise = new Promise(function (resolve) {
var _options;
return setTimeout(function () {
return resolve(false);
}, (_options = options) === null || _options === void 0 ? void 0 : _options.timeoutInMilliseconds);
});
var currentPromise = Promise.race([actionPromise, timeoutPromise]);
lastPromise.current = currentPromise;
currentPromise.then(function (res) {
// filtering last update promise
if (currentPromise === lastPromise.current) {
// filtering timeout
if (typeof res !== 'boolean') {
// filtering success
if (res.success) {
setResult(res.result);
} else {
if (!res.canceled) {
setError(res.error);
} else {
setIsCancel(true);
}
}
} else {
setIsTimeout(true);
}
}
})["catch"](function (e) {
if (currentPromise === lastPromise.current) {
console.error('useDispatchAsync: Unexpected error', e);
setError(e);
}
});
}, deps);
var status = react.useMemo(function () {
if (!result && !error && !isTimeout && !isCancel) {
return 'loading';
}
if (result) {
return 'success';
}
if (isCancel) {
return 'canceled';
}
if (error) {
return 'error';
}
if (isTimeout) {
return 'timeout';
}
return 'unknown';
}, [result, error, isTimeout, isCancel]);
switch (status) {
case 'loading':
case 'timeout':
case 'canceled':
return {
status: status
};
case 'success':
return {
result: result,
status: status
};
case 'error':
return {
error: error,
status: status
};
default:
return {
status: 'unknown'
};
}
}
exports.ConfigMiddleware = ConfigMiddleware;
exports.createDispatchAsyncMiddleware = createDispatchAsyncMiddleware;
exports.dispatchAsync = dispatchAsync;
exports.useCompatDispatchAsync = useCompatDispatchAsync;
exports.useDispatchAsync = useDispatchAsync;
//# sourceMappingURL=react-redux-dispatch-async.cjs.development.js.map
;