Skip to content

Commit 9c7a241

Browse files
committed
fix #1200 - all inputs missing acts like PreventUpdate
except when all inputs have multivalued wildcards and all outputs exist
1 parent 6a3ef75 commit 9c7a241

File tree

3 files changed

+573
-26
lines changed

3 files changed

+573
-26
lines changed

dash-renderer/src/actions/index.js

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,6 @@ function unwrapIfNotMulti(paths, idProps, spec, anyVals, depType) {
154154
']'
155155
);
156156
}
157-
// TODO: unwrapped list of wildcard ids?
158-
// eslint-disable-next-line no-console
159-
console.log(paths.objs);
160157
throw new ReferenceError(
161158
'A nonexistent object was used in an `' +
162159
depType +
@@ -247,20 +244,47 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
247244

248245
let payload;
249246
try {
250-
const outputs = allOutputs.map((out, i) =>
251-
unwrapIfNotMulti(
252-
paths,
253-
map(pick(['id', 'property']), out),
254-
cb.callback.outputs[i],
255-
cb.anyVals,
256-
'Output'
257-
)
258-
);
247+
const inVals = fillVals(paths, layout, cb, inputs, 'Input', true);
248+
249+
const preventCallback = () => {
250+
removeCallbackFromPending();
251+
// no server call here; for performance purposes pretend this is
252+
// a clientside callback and defer fireNext for the end
253+
// of the currently-ready callbacks.
254+
hasClientSide = true;
255+
return null;
256+
};
257+
258+
if (inVals === null) {
259+
return preventCallback();
260+
}
261+
262+
let outputs;
263+
try {
264+
outputs = allOutputs.map((out, i) =>
265+
unwrapIfNotMulti(
266+
paths,
267+
map(pick(['id', 'property']), out),
268+
cb.callback.outputs[i],
269+
cb.anyVals,
270+
'Output'
271+
)
272+
);
273+
} catch (e) {
274+
if (e instanceof ReferenceError && !flatten(inVals).length) {
275+
// This case is all-empty multivalued wildcard inputs,
276+
// which we would normally fire the callback for, except
277+
// some outputs are missing. So instead we treat it like
278+
// regular missing inputs and just silently prevent it.
279+
return preventCallback();
280+
}
281+
throw e;
282+
}
259283

260284
payload = {
261285
output,
262286
outputs: isMultiOutputProp(output) ? outputs : outputs[0],
263-
inputs: fillVals(paths, layout, cb, inputs, 'Input'),
287+
inputs: inVals,
264288
changedPropIds: keys(cb.changedPropIds),
265289
};
266290
if (cb.callback.state.length) {
@@ -360,14 +384,18 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
360384
updatePending(pendingCallbacks, without(updated, allPropIds));
361385
}
362386

363-
function handleError(err) {
387+
function removeCallbackFromPending() {
364388
const {pendingCallbacks} = getState();
365389
if (requestIsActive(pendingCallbacks, resolvedId, requestId)) {
366390
// Skip all prop updates from this callback, and remove
367391
// it from the pending list so callbacks it was blocking
368392
// that have other changed inputs will still fire.
369393
updatePending(pendingCallbacks, allPropIds);
370394
}
395+
}
396+
397+
function handleError(err) {
398+
removeCallbackFromPending();
371399
const outputs = payload
372400
? map(combineIdAndProp, flatten([payload.outputs])).join(', ')
373401
: output;
@@ -398,9 +426,12 @@ async function fireReadyCallbacks(dispatch, getState, callbacks) {
398426
return hasClientSide ? fireNext().then(done) : done;
399427
}
400428

401-
function fillVals(paths, layout, cb, specs, depType) {
429+
function fillVals(paths, layout, cb, specs, depType, allowAllMissing) {
402430
const getter = depType === 'Input' ? cb.getInputs : cb.getState;
403-
return getter(paths).map((inputList, i) =>
431+
const errors = [];
432+
let emptyMultiValues = 0;
433+
434+
const fillInputs = (inputList, i) =>
404435
unwrapIfNotMulti(
405436
paths,
406437
inputList.map(({id, property, path: path_}) => ({
@@ -411,8 +442,45 @@ function fillVals(paths, layout, cb, specs, depType) {
411442
specs[i],
412443
cb.anyVals,
413444
depType
414-
)
415-
);
445+
);
446+
447+
const tryFill = (inputList, i) => {
448+
try {
449+
const inputs = fillInputs(inputList, i);
450+
if (isMultiValued(specs[i]) && !inputs.length) {
451+
emptyMultiValues++;
452+
}
453+
return inputs;
454+
} catch (e) {
455+
if (e instanceof ReferenceError) {
456+
errors.push(e);
457+
return null;
458+
}
459+
// any other error we still want to see!
460+
throw e;
461+
}
462+
};
463+
464+
const inputVals = getter(paths).map(allowAllMissing ? tryFill : fillInputs);
465+
466+
if (errors.length) {
467+
if (errors.length + emptyMultiValues === inputVals.length) {
468+
// We have at least one non-multivalued input, but all simple and
469+
// multi-valued inputs are missing.
470+
// (if all inputs are multivalued and all missing we still return
471+
// them as normal, and fire the callback.)
472+
return null;
473+
}
474+
// If we get here we have some missing and some present inputs.
475+
// That's a real error, so rethrow the first missing error.
476+
// Wildcard reference errors mention a list of wildcard specs logged
477+
// TODO: unwrapped list of wildcard ids?
478+
// eslint-disable-next-line no-console
479+
console.log(paths.objs);
480+
throw errors[0];
481+
}
482+
483+
return inputVals;
416484
}
417485

418486
function handleServerside(config, payload, hooks) {

0 commit comments

Comments
 (0)