Closed KoboBunny closed 3 years ago
Right now there is no typings for Reanimated2 that's why VSCode shows an error.
There is PR in progress to fix this.
@KoboBunny, could please verify if that error still shows on the latest alpha?
@KoboBunny, could please verify if that error still shows on the latest alpha?
still an error
Hi @terrysahaidak
Sorry I didn't get a chance to get back to you! We're not using the alpha at the moment as we wanted to wait for the beta to come out and we could afford to wait. If we start using it again, I'll definitely let you know if we still have this error!
Hi @software-mansion I am trying to use the plugin for achieving Animation in React-Native project but facing this issue every-time. Tried with both static and dynamic values.
Hi @sourabhk-cg could you post more info? The code would be nice.
Hi @terrysahaidak , Following is the code hoping for your help...
import {
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import React, { useEffect, useState } from "react";
import { Animated, Keyboard, Text } from "react-native";
const dummyAnimation = (props: any) => {
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
"keyboardDidShow",
() => {
setIsKeyboardOpen(true);
}
);
const keyboardDidHideListener = Keyboard.addListener(
"keyboardDidHide",
() => {
setIsKeyboardOpen(false);
}
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
const borderRadius = useSharedValue(22);
const borderRadiusShowStyle = useAnimatedStyle(() => {
return {
width: withTiming(0, {
duration: 400,
}),
};
});
const borderRadiusHideStyle = useAnimatedStyle(() => {
return {
width: withTiming(borderRadius.value, {
duration: 400,
}),
};
});
return (
<Animated.View
style={[ isKeyboardOpen ? borderRadiusShowStyle : borderRadiusHideStyle ]}
>
<Text>{"this is animating"}</Text>
</Animated.View>
);
};
if you have just installed Reanimated, make sure to run packager again with --reset-cache
.
Also, for your animation you can do something like that:
const dummyAnimation = (props: any) => {
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
"keyboardDidShow",
() => {
setIsKeyboardOpen(true);
}
);
const keyboardDidHideListener = Keyboard.addListener(
"keyboardDidHide",
() => {
setIsKeyboardOpen(false);
}
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
const borderRadiusStyle = useAnimatedStyle(() => {
return {
width: withTiming(isKeyboardOpen ? 22 : 0, {
duration: 400,
}),
};
}, [isKeyboardOpen]);
return (
<Animated.View
style={borderRadiusStyle}
>
<Text>{"this is animating"}</Text>
</Animated.View>
);
};
if you have just installed Reanimated, make sure to run packager again with
--reset-cache
.Also, for your animation you can do something like that:
const dummyAnimation = (props: any) => { const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( "keyboardDidShow", () => { setIsKeyboardOpen(true); } ); const keyboardDidHideListener = Keyboard.addListener( "keyboardDidHide", () => { setIsKeyboardOpen(false); } ); return () => { keyboardDidHideListener.remove(); keyboardDidShowListener.remove(); }; }, []); const borderRadiusStyle = useAnimatedStyle(() => { return { width: withTiming(isKeyboardOpen ? 22 : 0, { duration: 400, }), }; }, [isKeyboardOpen]); return ( <Animated.View style={borderRadiusStyle} > <Text>{"this is animating"}</Text> </Animated.View> ); };
Hi @terrysahaidak, Thank you for your quick reply, but I am sorry to say that I tried your suggestions but still getting the same error in my code.
What version of Reanimated did you use?
What version of Reanimated did you use?
I am using following version: "react-native-reanimated": "^2.0.0-alpha.6" and following this document URL for installation. Installation Document
Could you try to reinstall node_modules? I mean remove them completely before installation
Could you try to reinstall node_modules? I mean remove them completely before installation
I tried removing and re-installing node_moduels and then completely removed application from emulator and build folders as well and then tried all installation steps and run the app, but still same result.
Sorry, then I won't be able to help without the access to code. But still, check out if the following functions exist in the react-native-reanimated in node_modules. That's the best place to start.
Sorry, then I won't be able to help without the access to code. But still, check out if the following functions exist in the react-native-reanimated in node_modules. That's the best place to start.
Okay, but the code I shared is the complete which I was trying for the first time on my page, and I checked for the function that is also available in node_modules of react-native-reanimated. Anyway thanks for your help. 👍
@sourabhk-cg, please show the content of node_modules/react-native-reanimated/src/index.js
, node_modules/react-native-reanimated/src/reanimated2/index.js
and node_modules/react-native-reanimated/src/reanimated2/Hooks.js
.
Your babel config may be helpful as well.
You may try clearing the watchman cache too, sometimes it helps.
src/reanimated2/index.js
export * from './core';
export * from './Hooks';
export * from './animations';
export * from './interpolation';
export * from './Easing';
export * from './NativeMethods';
export {default as processColor} from "./Colors";
src/reanimated2/Hooks.js
import { useEffect, useRef } from 'react';
import WorkletEventHandler from './WorkletEventHandler';
import { startMapper, stopMapper, makeMutable, makeRemote } from './core';
import updateProps from './UpdateProps';
import { initialUpdaterRun } from './animations';
import { getTag } from './NativeMethods';
export function useSharedValue(init) {
const ref = useRef(null);
if (ref.current === null) {
ref.current = {
mutable: makeMutable(init),
last: init,
};
} else if (init !== ref.current.last) {
ref.current.last = init;
ref.current.mutable.value = init;
}
return ref.current.mutable;
}
export function useMapper(fun, inputs = [], outputs = [], dependencies = []) {
useEffect(() => {
const mapperId = startMapper(fun, inputs, outputs);
return () => {
stopMapper(mapperId);
};
}, dependencies);
}
export function useEvent(handler, eventNames = [], rebuild = false) {
const initRef = useRef(null);
if (initRef.current === null) {
initRef.current = new WorkletEventHandler(handler, eventNames);
} else if (rebuild) {
initRef.current.worklet = handler;
}
return initRef.current;
}
function prepareAnimation(animatedProp, lastAnimation, lastValue) {
'worklet';
function prepareAnimation(animatedProp, lastAnimation, lastValue) {
if (Array.isArray(animatedProp)) {
animatedProp.forEach((prop, index) =>
prepareAnimation(
prop,
lastAnimation && lastAnimation[index],
lastValue && lastValue[index]
)
);
return animatedProp;
}
if (typeof animatedProp === 'object' && animatedProp.animation) {
const animation = animatedProp;
let value = animation.current;
if (lastValue !== undefined) {
if (typeof lastValue === 'object') {
if (lastValue.value !== undefined) {
// previously it was a shared value
value = lastValue.value;
} else if (lastValue.animation !== undefined) {
// it was an animation before, copy its state
value = lastAnimation.current;
}
} else {
// previously it was a plan value, just set it as starting point
value = lastValue;
}
}
animation.callStart = (timestamp) => {
animation.start(animation, value, timestamp, lastAnimation);
};
} else if (typeof animatedProp === 'object') {
// it is an object
Object.keys(animatedProp).forEach((key) =>
prepareAnimation(
animatedProp[key],
lastAnimation && lastAnimation[key],
lastValue && lastValue[key]
)
);
}
}
return prepareAnimation(animatedProp, lastAnimation, lastValue);
}
function runAnimations(animation, timestamp, key, result) {
'worklet';
function runAnimations(animation, timestamp, key, result) {
if (Array.isArray(animation)) {
result[key] = [];
let allFinished = true;
animation.forEach((entry, index) => {
if (!runAnimations(entry, timestamp, index, result[key])) {
allFinished = false;
}
});
return allFinished;
} else if (typeof animation === 'object' && animation.animation) {
if (animation.callStart) {
animation.callStart(timestamp);
animation.callStart = null;
}
const finished = animation.animation(animation, timestamp);
animation.timestamp = timestamp;
if (finished) {
animation.finished = true;
animation.callback && animation.callback(true /* finished */);
}
result[key] = animation.current;
return finished;
} else if (typeof animation === 'object') {
result[key] = {};
let allFinished = true;
Object.keys(animation).forEach((k) => {
if (!runAnimations(animation[k], timestamp, k, result[key])) {
allFinished = false;
}
});
return allFinished;
} else {
result[key] = animation;
return true;
}
}
return runAnimations(animation, timestamp, key, result);
}
// TODO: recirsive worklets aren't supported yet
function isAnimated(prop) {
'worklet';
function isAnimated(prop) {
if (Array.isArray(prop)) {
return prop.some(isAnimated);
}
if (typeof prop === 'object') {
if (prop.animation) {
return true;
}
return Object.keys(prop).some((key) => isAnimated(prop[key]));
}
return false;
}
return isAnimated(prop);
}
function styleDiff(oldStyle, newStyle) {
'worklet';
const diff = {};
Object.keys(oldStyle).forEach((key) => {
if (newStyle[key] === undefined) {
diff[key] = null;
}
});
Object.keys(newStyle).forEach((key) => {
const value = newStyle[key];
const oldValue = oldStyle[key];
if (isAnimated(value)) {
// do nothing
return;
}
if (
oldValue !== value &&
JSON.stringify(oldValue) !== JSON.stringify(value)
) {
// I'd use deep equal here but that'd take additional work and this was easier
diff[key] = value;
}
});
return diff;
}
function styleUpdater(viewTag, updater, state) {
'worklet';
const animations = state.animations || {};
const newValues = updater() || {};
const oldValues = state.last;
// extract animated props
let hasAnimations = false;
Object.keys(animations).forEach((key) => {
const value = newValues[key];
if (!isAnimated(value)) {
delete animations[key];
}
});
Object.keys(newValues).forEach((key) => {
const value = newValues[key];
if (isAnimated(value)) {
prepareAnimation(value, animations[key], oldValues[key]);
animations[key] = value;
hasAnimations = true;
}
});
function frame(timestamp) {
const { animations, last, isAnimationCancelled } = state;
if (isAnimationCancelled) {
state.isAnimationRunning = false;
return;
}
const updates = {};
let allFinished = true;
Object.keys(animations).forEach((propName) => {
const finished = runAnimations(
animations[propName],
timestamp,
propName,
updates
);
if (finished) {
last[propName] = updates[propName];
delete animations[propName];
} else {
allFinished = false;
}
});
if (Object.keys(updates).length) {
updateProps(viewTag.value, updates);
}
if (!allFinished) {
requestAnimationFrame(frame);
} else {
state.isAnimationRunning = false;
}
}
if (hasAnimations) {
state.animations = animations;
if (!state.isAnimationRunning) {
state.isAnimationCancelled = false;
state.isAnimationRunning = true;
requestAnimationFrame(frame);
}
} else {
state.isAnimationCancelled = true;
state.animations = {};
}
// calculate diff
const diff = styleDiff(oldValues, newValues);
state.last = Object.assign({}, oldValues, newValues);
if (Object.keys(diff).length !== 0) {
updateProps(viewTag.value, diff);
}
}
export function useAnimatedStyle(updater, dependencies) {
const viewTag = useSharedValue(-1);
const initRef = useRef(null);
const inputs = Object.values(updater._closure);
// build dependencies
if (dependencies === undefined) {
dependencies = [...inputs, updater.__workletHash];
} else {
dependencies.push(updater.__workletHash);
}
if (initRef.current === null) {
const initial = initialUpdaterRun(updater);
initRef.current = {
initial,
remoteState: makeRemote({ last: initial }),
};
}
const { remoteState, initial } = initRef.current;
useEffect(() => {
const fun = () => {
'worklet';
styleUpdater(viewTag, updater, remoteState);
};
const mapperId = startMapper(fun, inputs, []);
return () => {
stopMapper(mapperId);
};
}, dependencies);
// check for invalid usage of shared values in returned object
let wrongKey;
const isError = Object.keys(initial).some((key) => {
const element = initial[key];
const result = typeof element === 'object' && element.value !== undefined;
if (result) {
wrongKey = key;
}
return result;
});
if (isError && wrongKey !== undefined) {
throw new Error(
`invalid value passed to \`${wrongKey}\`, maybe you forgot to use \`.value\`?`
);
}
return {
viewTag,
initial,
};
}
// TODO: we should make sure that when useAP is used we are not assigning styles
// when you need styles to animated you should always use useAS
export const useAnimatedProps = useAnimatedStyle;
export function useDerivedValue(processor, dependencies) {
const initRef = useRef(null);
const inputs = Object.values(processor._closure);
// build dependencies
if (dependencies === undefined) {
dependencies = [...inputs, processor.__workletHash];
} else {
dependencies.push(processor.__workletHash);
}
if (initRef.current === null) {
initRef.current = makeMutable(initialUpdaterRun(processor));
}
const sharedValue = initRef.current;
useEffect(() => {
const fun = () => {
'worklet';
sharedValue.value = processor();
};
const mapperId = startMapper(fun, inputs, [sharedValue]);
return () => {
stopMapper(mapperId);
};
}, dependencies);
return sharedValue;
}
// builds one big hash from multiple worklets' hashes
function buildWorkletsHash(handlers) {
return Object.keys(handlers).reduce(
(previousValue, key) =>
previousValue === null
? handlers[key].__workletHash
: previousValue.toString() + handlers[key].__workletHash.toString(),
null
);
}
// builds dependencies array for gesture handlers
function buildDependencies(dependencies, handlers) {
if (dependencies === undefined) {
dependencies = Object.keys(handlers).map((handlerKey) => {
const handler = handlers[handlerKey];
return {
workletHash: handler.__workletHash,
closure: handler._closure,
};
});
} else {
dependencies.push(buildWorkletsHash(handlers));
}
return dependencies;
}
// this is supposed to work as useEffect comparison
function areDependenciesEqual(nextDeps, prevDeps) {
function is(x, y) {
/* eslint-disable no-self-compare */
return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
/* eslint-enable no-self-compare */
}
var objectIs = typeof Object.is === 'function' ? Object.is : is;
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) return !1;
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++)
if (!objectIs(nextDeps[i], prevDeps[i])) return !1;
return !0;
}
return areHookInputsEqual(nextDeps, prevDeps);
}
export function useAnimatedGestureHandler(handlers, dependencies) {
const initRef = useRef(null);
if (initRef.current === null) {
initRef.current = {
context: makeRemote({}),
savedDependencies: [],
};
}
const { context, savedDependencies } = initRef.current;
dependencies = buildDependencies(dependencies, handlers);
const dependenciesDiffer = !areDependenciesEqual(
dependencies,
savedDependencies
);
initRef.current.savedDependencies = dependencies;
return useEvent(
(event) => {
'worklet';
const FAILED = 1;
const BEGAN = 2;
const CANCELLED = 3;
const ACTIVE = 4;
const END = 5;
if (event.state === BEGAN && handlers.onStart) {
handlers.onStart(event, context);
}
if (event.state === ACTIVE && handlers.onActive) {
handlers.onActive(event, context);
}
if (event.oldState === ACTIVE && event.state === END && handlers.onEnd) {
handlers.onEnd(event, context);
}
if (
event.oldState === BEGAN &&
event.state === FAILED &&
handlers.onFail
) {
handlers.onFail(event, context);
}
if (
event.oldState === ACTIVE &&
event.state === CANCELLED &&
handlers.onCancel
) {
handlers.onCancel(event, context);
}
if (
(event.oldState === BEGAN || event.oldState === ACTIVE) &&
event.state !== BEGAN &&
event.state !== ACTIVE &&
handlers.onFinish
) {
handlers.onFinish(
event,
context,
event.state === CANCELLED || event.state === FAILED
);
}
},
['onGestureHandlerStateChange', 'onGestureHandlerEvent'],
dependenciesDiffer
);
}
export function useAnimatedScrollHandler(handlers, dependencies) {
const initRef = useRef(null);
if (initRef.current === null) {
initRef.current = {
context: makeRemote({}),
savedDependencies: [],
};
}
const { context, savedDependencies } = initRef.current;
dependencies = buildDependencies(dependencies, handlers);
const dependenciesDiffer = !areDependenciesEqual(
dependencies,
savedDependencies
);
initRef.current.savedDependencies = dependencies;
// build event subscription array
const subscribeForEvents = ['onScroll'];
if (handlers.onBeginDrag !== undefined) {
subscribeForEvents.push('onScrollBeginDrag');
}
if (handlers.onEndDrag !== undefined) {
subscribeForEvents.push('onScrollEndDrag');
}
if (handlers.onMomentumBegin !== undefined) {
subscribeForEvents.push('onMomentumScrollBegin');
}
if (handlers.onMomentumEnd !== undefined) {
subscribeForEvents.push('onMomentumScrollEnd');
}
return useEvent(
(event) => {
'worklet';
const {
onScroll,
onBeginDrag,
onEndDrag,
onMomentumBegin,
onMomentumEnd,
} = handlers;
if (event.eventName.endsWith('onScroll')) {
if (onScroll) {
onScroll(event, context);
} else if (typeof handlers === 'function') {
handlers(event, context);
}
} else if (onBeginDrag && event.eventName.endsWith('onScrollBeginDrag')) {
onBeginDrag(event, context);
} else if (onEndDrag && event.eventName.endsWith('onScrollEndDrag')) {
onEndDrag(event, context);
} else if (
onMomentumBegin &&
event.eventName.endsWith('onMomentumScrollBegin')
) {
onMomentumBegin(event, context);
} else if (
onMomentumEnd &&
event.eventName.endsWith('onMomentumScrollEnd')
) {
onMomentumEnd(event, context);
}
},
subscribeForEvents,
dependenciesDiffer
);
}
export function useAnimatedRef() {
const tag = useSharedValue(-1);
const ref = useRef(null);
if (!ref.current) {
const fun = function(component) {
'worklet';
// enters when ref is set by attaching to a component
if (component) {
tag.value = getTag(component);
fun.current = component;
}
return tag.value;
};
Object.defineProperty(fun, 'current', {
value: null,
writable: true,
enumerable: false,
});
ref.current = fun;
}
return ref.current;
}
/**
* @param prepare - worklet used for data preparation for the second parameter
* @param react - worklet which takes data prepared by the one in the first parameter and performs certain actions
* the first worklet defines the inputs, in other words on which shared values change will it be called.
* 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) {
const inputsRef = useRef(null);
if (inputsRef.current === null) {
inputsRef.current = {
inputs: Object.values(prepare._closure),
};
}
const { inputs } = inputsRef.current;
useEffect(() => {
const fun = () => {
'worklet';
const input = prepare();
react(input);
};
const mapperId = startMapper(fun, inputs, []);
return () => {
stopMapper(mapperId);
};
}, inputs);
}
Our node_modules does not have file named react-native-reanimated/index.js and our project does not have bable.config.js, should I create a new file for bable config? If yes will you be able to help me with the format?
If you don't have babel config how did you install Reanimated2 and use it's plugin then? I'm afraid I can't help you without reproducing repo.
Could you try to use import from "src"
import {
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated/src/Animated";
But also it's weird your project doesn't have a babel config file. At least the default one should be
Hi @terrysahaidak, sorry to ping you again for same, I just tried the plugin with newly created fresh application, and followed the given document approach. But as soon as I put my app in debug mode it crashes, it was happening with my existing project so I tried with fresh one but same result. Will you be able to give some insight on this? In new project we have the babel.config.js as expected and we defined the properties as well.
Could you try to use import from "src"
import { useAnimatedStyle, useSharedValue, withTiming, } from "react-native-reanimated/src/Animated";
But also it's weird your project doesn't have a babel config file. At least the default one should be
I tried this solution as well but this also did not work.
Hi @terrysahaidak, sorry to ping you again for same, I just tried the plugin with newly created fresh application, and followed the given document approach. But as soon as I put my app in debug mode it crashes, it was happening with my existing project so I tried with fresh one but same result. Will you be able to give some insight on this? In new project we have the babel.config.js as expected and we defined the properties as well.
Unfortunately, debug mode is not supported. The app won't work because of turbo modules.
This is known limitation of Reanimated and TurboModules itself. Please, read carefully the limitations sections of the docs.
if you have just installed Reanimated, make sure to run packager again with
--reset-cache
.Also, for your animation you can do something like that:
const dummyAnimation = (props: any) => { const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( "keyboardDidShow", () => { setIsKeyboardOpen(true); } ); const keyboardDidHideListener = Keyboard.addListener( "keyboardDidHide", () => { setIsKeyboardOpen(false); } ); return () => { keyboardDidHideListener.remove(); keyboardDidShowListener.remove(); }; }, []); const borderRadiusStyle = useAnimatedStyle(() => { return { width: withTiming(isKeyboardOpen ? 22 : 0, { duration: 400, }), }; }, [isKeyboardOpen]); return ( <Animated.View style={borderRadiusStyle} > <Text>{"this is animating"}</Text> </Animated.View> ); };
Hi @terrysahaidak , I referred the document's limitation section and found out I can work with flipper so I did the setup of that and now able to see log, but in above code as you corrected me earlier, I am getting 2 odd things,
By any chance will you be able to help please?
We hotfixed it in our jest.setup file, by doing this:
const mock = jest.requireMock("react-native-reanimated");
jest.mock("react-native-reanimated", () => {
return {
...mock,
useSharedValue: jest.fn,
useAnimatedStyle: jest.fn,
};
});
however, I am not sure if the component which is being tested still behaves correctly with that.
Closing as duplicate of #1195.
We hotfixed it in our jest.setup file, by doing this:
const mock = jest.requireMock("react-native-reanimated"); jest.mock("react-native-reanimated", () => { return { ...mock, useSharedValue: jest.fn, useAnimatedStyle: jest.fn, }; });
however, I am not sure if the component which is being tested still behaves correctly with that.
requireMock("react-native-reanimated/mock");
worked for me.
TypeError: _react.default.useRef.mockReturnValueOnce is not a function
Description
useSharedValue throws an error that it's not a function when using jest and react-native-testing-library. I've mocked react-native-reanimated by using the mock provided as part of the package. I also notice VS Code highlights that reanimated has no exported member useSharedValue, useAnimatedStyle and withTiming, however the component still runs properly without any errors.
Does anyone know what needs to be done to mock it properly?
Screenshots
Steps To Reproduce
Expected behavior
I expected the render test to pass since I'm successfully mocking react-native-reanimated
Actual behavior
The render test fails due to useSharedValue not being a function.
"TypeError: (0 , _reactNativeReanimated.useSharedValue) is not a function"
Snack or minimal code example
The Test to see if the component renders
`import React from "react"; import { render } from "react-native-testing-library"; import { Switch } from "../src/native/Switch";
describe("Switch Package", () => { it("renders", () => { render( );
});
});`
Mock file which is placed in mocks named react-native-reanimated.js
jest.mock("react-native-reanimated", () => require("react-native-reanimated/mock") );
The Component using Reanimated - Switch.tsx `import { Button } from 'react-native'; import { useSharedValue, useAnimatedStyle, withTiming, Easing, } from 'react-native-reanimated';
function Switch() { const opacity = useSharedValue(0);
const style = useAnimatedStyle(() => { return { opacity: withTiming(opacity.value, { duration: 200, easing: sing.out(Easing.cubic), }), }; });
return (
); }`
Package versions