Skip to content

Instantly share code, notes, and snippets.

@SudoPlz
Last active October 13, 2025 14:41
Show Gist options
  • Select an option

  • Save SudoPlz/54e8b728253fc915dfc9fdfe701ce5b1 to your computer and use it in GitHub Desktop.

Select an option

Save SudoPlz/54e8b728253fc915dfc9fdfe701ce5b1 to your computer and use it in GitHub Desktop.

Reduced Motion + React-Freeze Bug Analysis

Problem Summary

Issue: Gray screen appears on Android when users enable "Reduce Motion" accessibility setting and quickly swipe between channels.

Root Cause: Animation updates (translateX) arrive while components are frozen/unregistered, causing updates to be lost and components to render with stale values.

Context: I've been working on this issue and there are currently three different solutions in play. I want to present the situation to the team and get alignment on the best approach.

Technical Timeline

  1. User enables reduced motion → Animations complete instantly
  2. User swipes to close channeltranslateX becomes 411px (off-screen)
  3. Component freezesComponentRegistry.unregister() removes component
  4. User immediately swipes to opentranslateX updates to 0
  5. Update arrives while frozenComponentRegistry.getComponent() returns undefined
  6. Update is lost → No way to apply translateX: 0
  7. Component unfreezes → Renders with old translateX: 411px (gray screen)

Three Available Solutions

A) My Solution: Cached Updates ✅ RECOMMENDED

Link Here Implementation: Cache updates when components are unregistered, apply on re-registration.

// ComponentRegistry.ts
const pendingUpdates = new Map<number | HTMLElement, any>();

register: (tag, component) => {
  componentRegistry.set(tag, component);
  const pendingUpdate = pendingUpdates.get(tag);
  if (pendingUpdate) {
    component._updateReanimatedProps(pendingUpdate);
    pendingUpdates.delete(tag);
  }
},

cacheUpdate: (tag, props) => {
  pendingUpdates.set(tag, props);
}

// updateProps.ts
function updatePropsOnReactJS(tag: number, props: StyleProps) {
  const component = ComponentRegistry.getComponent(tag);
  if (component) {
    component._updateReanimatedProps(props);
  } else {
    ComponentRegistry.cacheUpdate(tag, props); // Cache when component not found
  }
}

Pros:

  • ✅ Fixes root cause (lost updates during freeze)
  • ✅ Clean, targeted solution
  • ✅ No conflicts with Reanimated v4 migration

Cons:

  • ❌ Requires maintaining fork of Reanimated
  • ❌ Currently commented out in our codebase (needs to be uncommented)

B) Reanimated Team Solution: Style Re-registration ❌ WON'T WORK

Link here Implementation: Check if styles are actually attached before skipping re-registration.

// ViewDescriptorsSet.ts
const viewTags = new Set<number>();
has: (viewTag: number) => viewTags.has(viewTag),

// createAnimatedComponent.tsx
const isStyleAttached = (style: StyleProps) =>
  style.viewDescriptors.has(viewTag);

if (hasOneSameStyle && isStyleAttached(prevStyles[0])) {
  return; // Only skip if already attached
}

Why it doesn't work for our case:

  • ❌ Assumes component exists to receive updates
  • ❌ Our problem: component doesn't exist when updates arrive
  • ❌ Addresses different issue (style re-registration vs lost updates)

C) Discord Team Solution: freezeValue Workaround ⚠️ ** MAY be redundant**

Link Here Implementation: Force style recomputation after unfreeze with timeout.

// MainTabsChannelScreenStack.tsx
const freezeValue = useSharedValue(0);
React.useEffect(() => {
  const timeout = setTimeout(() => {
    freezeValue.set(freezeValue.get() + 1);
  }, 70);
}, [freeze, freezeValue]);

// useMainTabsChannelScreenStyles.tsx
const animatedStyles = useAnimatedStyle(() => {
  freezeValue?.get(); // Force recomputation
  return { transform: [{translateX: translateX.get()}] };
});

Pros:

  • ✅ Works (fixes gray screen)
  • ✅ No Reanimated fork needed

Cons:

  • ❌ Causes flicker (10ms delay) where the screen is still invisible, and then suddenly appears
  • ❌ Workaround, not root cause fix
  • ❌ May be redundant with cached updates solution (needs verification)

Why Reanimated Team's Solution Won't Work by itself

The Reanimated team's fix addresses style re-registration after freeze/unfreeze cycles. Our issue is fundamentally different:

  • Their problem: Style object exists but isn't attached to view
  • Our problem: Style updates arrive when component doesn't exist at all
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment