Skip to content

Commit

Permalink
fix: CSS transitions crash when react-freeze is used (#6942)
Browse files Browse the repository at this point in the history
## Summary

Fix crash caused by enabling freeze (from `react-freeze`) in
`react-native-screens` when CSS transitions are used on the freezed
screen.

## Example recordings

| Before | After |
|-|-|
| <video
src="https://github.com/user-attachments/assets/7d584fb8-3bae-4274-bacd-de5eb6543df5"
/> | <video
src="https://github.com/user-attachments/assets/4908bcd4-dab9-4352-967a-aaca3e0146df"
/> |

## Test plan

Copy and paste the following code to the playground example in the
example app:

<details>
  <summary>Source code</summary>

```tsx
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useLayoutEffect, useReducer, useRef } from 'react';
import { Keyboard, Text, TextInput, View } from 'react-native';
import Animated from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { enableFreeze } from 'react-native-screens';

import { flex } from '@/theme';

enableFreeze(true);

const SearchBar = () => {
  const inset = useSafeAreaInsets();
  const [isFocused, toggle] = useReducer((s) => !s, false);
  const ref = useRef<View>(null);
  const rect = useRef({ height: 0, width: 0, x: 0, y: 0 });

  useLayoutEffect(() => {
    // Even though ref.current is never null with Fabric, adding a typeguard to please the linter
    if (ref.current) {
      // @ts-expect-error - unstable_getBoundingClientRect, types outdated
      rect.current = ref.current.unstable_getBoundingClientRect();
    }
  }, []);

  return (
    <Animated.View
      style={{
        backgroundColor: isFocused
          ? 'rgba(255, 0, 0, 0.6)'
          : 'rgba(255, 255, 0, 0.8)',
        height: isFocused ? 60 + inset.top : 100 + inset.top,
        paddingTop: inset.top,
        transitionDuration: 250,
        transitionProperty: ['height', 'backgroundColor'],
      }}>
      <Animated.View style={{ flexDirection: 'row', height: '100%' }}>
        <Animated.View
          style={{
            alignItems: 'center',
            flex: 1,
            justifyContent: 'center',
            paddingHorizontal: 16,
          }}>
          <TextInput
            clearButtonMode="while-editing"
            keyboardType="web-search"
            placeholder="Search"
            placeholderTextColor="black"
            style={{
              backgroundColor: '#e5e5e5',
              borderRadius: 12,
              height: 48,
              paddingHorizontal: 16,
              width: '100%',
            }}
            onBlur={() => toggle()}
            onFocus={() => toggle()}
          />
        </Animated.View>
        <Animated.View
          style={{
            paddingRight: !isFocused ? 0 : rect.current.width,
            transitionDuration: 250,
            transitionProperty: ['paddingRight'],
          }}
        />
        <Animated.View
          style={{
            alignItems: 'center',
            height: '100%',
            justifyContent: 'center',
            opacity: isFocused ? 1 : 0,
            position: 'absolute',
            right: 0,
            transitionDuration: 250,
            transitionProperty: ['opacity'],
          }}>
          <View ref={ref} style={{ paddingRight: 16 }}>
            <Text
              numberOfLines={1}
              style={{ color: 'white', fontWeight: '600', textAlign: 'center' }}
              onPress={() => Keyboard.dismiss()}>
              Cancel
            </Text>
          </View>
        </Animated.View>
      </Animated.View>
    </Animated.View>
  );
};

const BottomBar = createBottomTabNavigator();

function SearchScreen() {
  return (
    <View style={[flex.fill, { backgroundColor: 'green' }]}>
      <SearchBar />
    </View>
  );
}

function HomeScreen() {
  return (
    <View style={[flex.fill, { backgroundColor: 'blue' }]}>
      <Text>Home</Text>
    </View>
  );
}

export default function Playground() {
  return (
    <View style={[flex.fill, { marginBottom: 100 }]}>
      <BottomBar.Navigator>
        <BottomBar.Screen component={HomeScreen} name="HomeScreen" />
        <BottomBar.Screen component={SearchScreen} name="SearchScreen" />
      </BottomBar.Navigator>
    </View>
  );
}
```
</details>
  • Loading branch information
MatiPl01 authored Jan 27, 2025
1 parent 0e1acfd commit 82baa6f
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ void StaticPropsRegistry::set(
remove(viewTag);
} else {
const auto newProps = dynamicFromValue(rt, props);
notifyObservers(rt, viewTag, valueFromDynamic(rt, get(viewTag)), props);
if (has(viewTag)) {
notifyObservers(rt, viewTag, valueFromDynamic(rt, get(viewTag)), props);
}
registry_[viewTag] = newProps;
}
}
Expand All @@ -24,6 +26,10 @@ folly::dynamic StaticPropsRegistry::get(const Tag viewTag) const {
return it->second;
}

bool StaticPropsRegistry::has(const Tag viewTag) const {
return registry_.find(viewTag) != registry_.end();
}

void StaticPropsRegistry::remove(const Tag viewTag) {
registry_.erase(viewTag);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class StaticPropsRegistry {
public:
void set(jsi::Runtime &rt, Tag viewTag, const jsi::Value &props);
folly::dynamic get(Tag viewTag) const;
bool has(Tag viewTag) const;
void remove(Tag viewTag);

bool hasObservers(const Tag viewTag) const;
Expand Down

0 comments on commit 82baa6f

Please sign in to comment.