react-native-ease is a React Native library that provides declarative, native-powered animations via a single EaseView component. It uses Core Animation (iOS) and ObjectAnimator/SpringAnimation (Android) — no JS animation loop, no worklets, no C++ runtime.
Fabric (new architecture) only. Does not support the old architecture.
src/ # TypeScript source (library)
EaseView.tsx # React component — flattens props to native
EaseViewNativeComponent.ts # Codegen spec — defines native props/events
types.ts # Public TypeScript types
index.tsx # Public exports
__tests__/ # Jest tests
ios/
EaseView.h # Native view header (ObjC++)
EaseView.mm # Native view implementation (Core Animation)
android/src/main/java/com/ease/
EaseView.kt # Native view (ObjectAnimator/SpringAnimation)
EaseViewManager.kt # ViewManager with @ReactProp setters
EasePackage.kt # Package registration
example/ # Demo app (separate workspace)
src/App.tsx # Main demo screen with animation examples
src/ComparisonScreen.tsx # Comparison with Reanimated
src/components/ # Shared demo components (Section, Button, TabBar)
The JS component (EaseView.tsx) takes structured props (animate, transition, etc.) and flattens them into individual native props defined in the codegen spec (EaseViewNativeComponent.ts). When those flat props change on the native side, the native view diffs previous vs new values and creates platform-native animations.
Prop flattening example:
animate={{ opacity: 0.5, scale: 1.2 }} → animateOpacity=0.5, animateScale=1.2
transition={{ type: 'spring', damping: 10 }} → transitionType="spring", transitionDamping=10
Key design pattern: All animation logic lives on the native side. The JS layer is purely a prop resolver — no animation state, no timers, no refs.
- Add to
AnimatePropsinsrc/types.ts - Add
animate<Prop>andinitialAnimate<Prop>codegen props insrc/EaseViewNativeComponent.ts - Add default to
IDENTITYinsrc/EaseView.tsxand pass the flat props toNativeEaseView - iOS: Handle the new property in
updateProps:— diff old/new, read presentation value, create animation - Android: Add
pending<Prop>field,@ReactPropsetter inEaseViewManager.kt, and handle inapplyAnimateValues() - Recycle: Reset the new property to its identity value in
prepareForRecycle(iOS) andcleanup()(Android). Fabric recycles views — any property not reset will leak stale values to the next user of the view. - Add tests and update README
- Add an example/demo in the example app (
example/src/App.tsxor a new screen)
Important: When adding or changing props/features, also update:
README.md(props table + usage section)docs/docs/usage.mdx(usage guide)docs/docs/api-reference.mdx(API reference table)skills/react-native-ease-refactor/SKILL.md(supported properties list, transition category keys, decision tree)
yarn # Install dependencies
yarn test # Jest tests (JS layer)
yarn lint # ESLint + TypeScript check
yarn format:check # Prettier + clang-format check
yarn format:write # Auto-fix formatting
yarn prepare # Build JS module (bob build)
yarn example start # Start Metro for example app
yarn example ios # Build and run example on iOS
yarn example android # Build and run example on AndroidBefore committing, always run:
yarn format:write && yarn lint && yarn testImportant: Run yarn format:write (not format:check) first to auto-fix formatting before linting. This avoids redundant Prettier errors in lint output.
Lefthook pre-commit hooks enforce ESLint and TypeScript checks. Fix all failures before committing.
Formatting tools:
- Prettier for TypeScript/JavaScript files
- clang-format for Objective-C++, Kotlin, and C++ files
React Native Codegen generates the native component interface from src/EaseViewNativeComponent.ts. The generated spec name is EaseViewSpec.
After changing the codegen spec, you need to rebuild the example app for the native side to pick up changes. On iOS, pod install in example/ios/ may also be needed.
- Unit tests are in
src/__tests__/and test JS prop resolution and native prop passing - Uses
@testing-library/react-nativewith a mocked native component - No native integration tests — test native behavior by running the example app on a device/simulator
- Example app has demos for all features; use it to verify changes visually
Use conventional commits: feat:, fix:, chore:, docs:, etc.
- Style/animate conflict:
stylecan contain anyViewStyleproperty. If a property appears in bothstyleandanimate, the animated value wins and the style value is stripped. A dev warning is logged. Properties likeopacityinstylework fine when not inanimate— the bitmask tells native which properties are animated vs style-managed. - View recycling: Fabric recycles native views.
prepareForRecycle(iOS) andcleanup()(Android) must reset ALL mutable view properties (opacity, translation, scale, rotation) to identity. Missing a reset causes stale values to leak across hot reloads or view reuse. - translateX/translateY are in DIPs (density-independent pixels) on the JS side. Android
EaseViewManagerconverts to pixels viaPixelUtil.toPixelFromDIP(). iOS codegen handles this automatically. - iOS uses
CALayerkey-path animations (transform.scale.x,transform.translation.x, etc.), not thetransformproperty directly. This meansanchorPointcontrols the pivot for scale/rotation. - iOS
anchorPointchanges shift visual position. TheupdateLayoutMetrics:oldLayoutMetrics:override compensates for this — don't change it without understanding the position math. - Spring damping ratio on Android is derived from
damping,stiffness, andmassusing:dampingRatio = damping / (2 * sqrt(stiffness * mass)). iOS passes these values directly toCASpringAnimation. - Animation batching: Both platforms track animation batches with a generation ID. When new animations start, any pending old-batch callbacks are fired as interrupted (
finished: false). - Loop only works with timing animations, not springs. Loop requires
initialAnimateto define the start value.