Skip to content

Commit

Permalink
Add previous result to useAnimatedReaction (#1653)
Browse files Browse the repository at this point in the history
## Description

In some cases, we don't want to run the `reaction` callback from `useAnimatedReaction` if the value received from the `preparing` callback doesn't change. This equality could be checked always and calling the `reaction` callback would be then conditional. However, it's better to leave the decision to the user as there are as well cases when we still want to run the `reaction` even if its argument doesn't change.

To solve the problem, keeping backward compatibility, the value of the previous call of the `preparing` callback is now being passed to the `reaction`.

Besides the cases that were mentioned here, it lets users perform more custom operations.

## Code example

```Js
const x = useSharedValue(0);
const flag = useSharedValue(false);
useAnimatedReaction(
  () => {
    return x.value > 125 && flag.value;
  },
  (data, previous) => {
    if (data !== previous) {
		// only if it changes, perform some actions
    }
  }
);
```

## Checklist

- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [x] Added TS types tests
- [x] Added unit / integration tests
- [x] Updated documentation
- [x] Ensured that CI passes
  • Loading branch information
karol-bisztyga committed Jan 22, 2021
1 parent 0b5cb09 commit fd5c6ad
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 7 deletions.
29 changes: 28 additions & 1 deletion Example/test/AnimatedReactionTest.js
Expand Up @@ -10,9 +10,11 @@ import Animated, {
const AnimatedReactionTest = () => {
const x = useSharedValue(0);
const x2 = useSharedValue(0);
const x3 = useSharedValue(0);

const maxX2 = 80;

// UAR #1
useAnimatedReaction(
() => {
return x.value / 1.5;
Expand All @@ -24,6 +26,18 @@ const AnimatedReactionTest = () => {
}
);

// UAR #2
useAnimatedReaction(
() => {
return x.value > 125 ? x.value : null;
},
(data, previous) => {
if (data !== previous) {
x3.value = data - 125;
}
}
);

const uas = useAnimatedStyle(() => {
'worklet';
return {
Expand All @@ -34,6 +48,7 @@ const AnimatedReactionTest = () => {
],
};
});

const uas2 = useAnimatedStyle(() => {
'worklet';
return {
Expand All @@ -45,6 +60,17 @@ const AnimatedReactionTest = () => {
};
});

const uas3 = useAnimatedStyle(() => {
'worklet';
return {
transform: [
{
translateX: withTiming(x3.value),
},
],
};
});

const handle = () => {
x.value = x.value + 25;
};
Expand All @@ -62,11 +88,12 @@ const AnimatedReactionTest = () => {
<View>
<Text>
The second box should follow the first one for the first 5 moves(then it
should stop)
should stop). After that, the third box should start moving.
</Text>
<Button title="Move" onPress={handle} />
<Animated.View style={[styles.box, uas]} />
<Animated.View style={[styles.box, uas2]} />
<Animated.View style={[styles.box, uas3]} />
</View>
);
};
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/api/useAnimatedReaction.md
Expand Up @@ -14,7 +14,7 @@ worklet used for data preparation for the second parameter. It also defines the

#### `react` [Function]

worklet which takes data prepared by the one in the first parameter and performs some actions. It can modify any shared values but those which are mentioned in the first worklet. Beware of that, because this may result in endless loop and high cpu usage.
worklet which takes data prepared by the `prepare` callback (being the first parameter of the hook) and performs some actions. As a second parameter it receives a result of the previous `prepare` call(starting with `null`). It can modify any shared values but those which are mentioned in the first worklet. Beware of that, because this may result in endless loop and high cpu usage.

#### `dependencies` [Array]

Expand All @@ -30,8 +30,10 @@ const App = () => {

const derived = useAnimatedReaction(() => {
return sv1.value * state;
}, (result) => {
sv2.value = result - 5;
}, (result, previous) => {
if (result !== previous) {
sv2.value = result - 5;
}
}, dependencies);
//...
return <></>
Expand Down
9 changes: 9 additions & 0 deletions react-native-reanimated-tests.tsx
Expand Up @@ -534,6 +534,15 @@ function UseAnimatedReactionTest() {
[state]
);

useAnimatedReaction(
() => {
return sv.value;
},
(value, previousResult) => {
console.log(value, previousResult);
}
);

return null;
}

Expand Down
4 changes: 2 additions & 2 deletions react-native-reanimated.d.ts
Expand Up @@ -485,8 +485,8 @@ declare module 'react-native-reanimated' {
): DerivedValue<T>;

export function useAnimatedReaction<D>(
dependencies: () => D,
effects: (dependencies: D) => void,
prepare: () => D,
react: (prepareResult: D, preparePreviousResult: D | null) => void,
deps?: DependencyList
): void;

Expand Down
4 changes: 3 additions & 1 deletion src/reanimated2/Hooks.js
Expand Up @@ -663,6 +663,7 @@ export function useAnimatedRef() {
* the second one can modify any shared values but those which are mentioned in the first worklet. Beware of that, because this may result in endless loop and high cpu usage.
*/
export function useAnimatedReaction(prepare, react, dependencies) {
const previous = useSharedValue(null);
if (dependencies === undefined) {
dependencies = [
Object.values(prepare._closure),
Expand All @@ -678,7 +679,8 @@ export function useAnimatedReaction(prepare, react, dependencies) {
const fun = () => {
'worklet';
const input = prepare();
react(input);
react(input, previous.value);
previous.value = input;
};
const mapperId = startMapper(fun, Object.values(prepare._closure), []);
return () => {
Expand Down

0 comments on commit fd5c6ad

Please sign in to comment.