necolas/react-native-web · error · Error
Touchable cannot transition from `${curState}` to `${signal}
Error message
Touchable cannot transition from `${curState}` to `${signal}` for responder `${responderID}` What it means
Companion to the unrecognized-signal throw: the transition lookup succeeded but maps to States.ERROR, meaning the state machine explicitly forbids moving from curState via this signal. Touchable throws to surface the illegal transition with the responder ID. Indicates a broken responder lifecycle or library bug.
Source
Thrown at packages/react-native-web/src/exports/Touchable/index.js:778
const responderID = this.state.touchable.responderID;
const curState = this.state.touchable.touchState;
const nextState = Transitions[curState] && Transitions[curState][signal];
if (!responderID && signal === Signals.RESPONDER_RELEASE) {
return;
}
if (!nextState) {
throw new Error(
'Unrecognized signal `' +
signal +
'` or state `' +
curState +
'` for Touchable responder `' +
responderID +
'`'
);
}
if (nextState === States.ERROR) {
throw new Error(
'Touchable cannot transition from `' +
curState +
'` to `' +
signal +
'` for responder `' +
responderID +
'`'
);
}
if (curState !== nextState) {
this._performSideEffectsForTransition(curState, nextState, signal, e);
this.state.touchable.touchState = nextState;
}
},
_cancelLongPressDelayTimeout: function () {
this.longPressDelayTimeout && clearTimeout(this.longPressDelayTimeout);
this.longPressDelayTimeout = null;View on GitHub (pinned to a9de220ba9)
Solutions
- Upgrade react-native-web to pick up state-machine race fixes.
- Reproduce and file an issue with the reported curState → signal pair.
- Avoid calling Touchable responder internals directly from app code.
- Simplify nesting of touchable/gesture components around the failing responder.
Example fix
// before (app code forcing internal state) ref._receiveSignal(Signals.ENTER_PRESS_PRESSIN, id); // after // rely on native pointer events; upgrade react-native-web instead
Defensive patterns
Strategy: try-catch
Validate before calling
// Only legal transitions should ever be dispatched:
const nextState = Transitions[curState] && Transitions[curState][signal];
if (nextState === States.ERROR) throw new Error(`Illegal transition ${curState} -> ${signal}`); Type guard
const isLegalTransition = (state, signal) => Transitions[state]?.[signal] !== States.ERROR;
Try / catch
try { pressHandlers(); } catch (e) { if (/cannot transition from/.test(e.message)) reportStateRace(e.message); else throw e; } Prevention
- Don't drive responder state changes manually
- Debounce/dedupe pointer events that can hit the same Touchable twice
- Check the react-native-web changelog for Touchable race fixes before debugging app code
When it happens
Trigger: A signal arrives that the transition table deliberately maps to ERROR for the current state — e.g. a second RESPONDER_GRANT while already active, or a termination/enter signal in a terminal state.
Common situations: Fuzzy/overlapping pointer events hitting the same Touchable twice; touch cancel sequences racing with release; custom long-press/press-in logic forcing state changes; known library race conditions fixed in newer versions.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Unrecognized signal `${signal}` or state `${curState}` for T
- Image: asset with ID "${source}" could not be found. Please
- The <Image> component cannot contain children. If you want t
- Invalid CSS keyframes type: ${typeof keyframesValue}
- StyleSheet.compose() only accepts 2 arguments, received ${le
AI-assisted analysis of necolas/react-native-web@a9de220ba9 (2026-09-01).
Data as JSON: /api/errors/bcf918cc7d641473.
Report an issue: GitHub.