# Recipe: swipe to spin

> Copy-paste hook that spins any object at a resting speed and lets the user fling it faster with a horizontal swipe that coasts and slows down, all on the UI thread.

Spin an object at a resting speed and let the user fling it faster with a horizontal swipe. On release it keeps turning and slows down. Everything runs on the UI thread; nothing re-renders.

**Copy `useSwipeRotation.ts` into your app**, then use it:

```tsx title="SpinningMoney.tsx"
import { MoneyStack } from "@apricot/assets";
import { Group, Scene, Spin } from "@apricot/react";
import { GestureDetector } from "react-native-gesture-handler";
import { useSwipeRotation } from "./useSwipeRotation";

export function SpinningMoney() {
  const { gesture, rotation } = useSwipeRotation();
  return (
    <GestureDetector gesture={gesture}>
      <Scene style={{ flex: 1 }}>
        <Spin speed={0.6}>
          <Group rotation={rotation}>
            <MoneyStack bills={40} />
          </Group>
        </Spin>
      </Scene>
    </GestureDetector>
  );
}
```

The app root must be wrapped in `GestureHandlerRootView` (see [Installation](https://matthiasbrandolin.dev/apricot/installation)).

## The hook

```ts title="useSwipeRotation.ts"
// Apricot recipe: swipe-to-spin
// Copy this file into your app. A horizontal swipe rotates whatever you wrap
// in <Group rotation={rotation}>; on release it keeps turning and slows down
// (Reanimated withDecay). Everything runs on the UI thread.
import { Gesture } from "react-native-gesture-handler";
import { cancelAnimation, useDerivedValue, useSharedValue, withDecay } from "react-native-reanimated";

export interface SwipeRotationOptions {
  /** Radians per point of finger movement. */
  sensitivity?: number;
  /** withDecay deceleration: closer to 1 coasts longer. */
  deceleration?: number;
}

export function useSwipeRotation({ sensitivity = 0.012, deceleration = 0.997 }: SwipeRotationOptions = {}) {
  const angle = useSharedValue(0);
  const rotation = useDerivedValue<readonly [number, number, number]>(() => [0, angle.value, 0]);
  const gesture = Gesture.Pan()
    .onBegin(() => {
      cancelAnimation(angle);
    })
    .onChange((event) => {
      angle.value += event.changeX * sensitivity;
    })
    .onEnd((event) => {
      angle.value = withDecay({ velocity: event.velocityX * sensitivity, deceleration });
    });
  return { gesture, rotation, angle };
}
```

It needs `react-native-gesture-handler` and `react-native-reanimated`.

## Options and result

| Option | Default | Meaning |
| --- | --- | --- |
| `sensitivity` | `0.012` | Radians per point of finger movement |
| `deceleration` | `0.997` | `withDecay` deceleration: closer to 1 coasts longer |

| Returns | Type | Use |
| --- | --- | --- |
| `gesture` | Pan gesture | Pass to `<GestureDetector gesture>` around the scene |
| `rotation` | `SharedValue<[0, angle, 0]>` | Pass to `<Group rotation>` around the object |
| `angle` | `SharedValue<number>` | The current angle in radians, to read or set (for example `angle.value = withSpring(0)` to reset) |

## How it works

`<Spin>` adds a constant rotation; the swipe drives a separate `rotation` shared value on the UI thread. Apricot mirrors shared values into the render loop without blocking the JS thread, and wakes the loop when they change, so with `frameloop="demand"` the scene sleeps again once everything stops. (Here `<Spin>` keeps it awake; remove it for an object that only turns when swiped.)

## Variations

- **Swipe only, no idle spin:** drop the `<Spin>` wrapper.
- **Tilt as well:** add a vertical pan that writes the x angle, and derive `[x, y, 0]` (see [Gestures and interaction](https://matthiasbrandolin.dev/apricot/guides/interaction#gestures-with-shared-values)).
- **A model:** put a `<Model>` inside the `<Group>`, or pass `rotation` to the `<Model>` directly.

## Related

- [Animation and shared values](https://matthiasbrandolin.dev/apricot/concepts/animation).
- [Motion reference](https://matthiasbrandolin.dev/apricot/reference/motion): `Spin`.
