-
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 7 replies
-
Hi @mgcrea. Check out our new Troubleshooting section - you might find some more general information about this. About transpiling with Reanimated Babel plugin before publishing - that's exactly what you shouldn't do. Our plugin is an integral part of our JavaScript code and What is important to check if your generated JS code (unless you publish source only) contains all the If even with those steps the code still doesn't work as expected, I'm happy to help investigate the causes of that. |
Beta Was this translation helpful? Give feedback.
-
Thank you for your detailed answer, I am indeed witnessing a different in behaviour when I build my library without the plugin. Does the babel plugin is supposed to compile worklets found in Not quite easy to give you a reproducible test case since it requires two projects with a build step but here is the big picture: Issue is with my drag&drop implementation react-native-dnd: The lib provides a Then to use a draggable you rely on a custom hook export const useDraggable(): => {
const layout = useSharedValue<LayoutRectangle>({
x: 0,
y: 0,
width: 0,
height: 0,
});
useLayoutEffect(() => {
const runLayoutEffect = () => {
"worklet";
draggableLayouts.value[id] = layout;
};
runOnUI(runLayoutEffect)();
}, [id]);
} Then, on the library side, there is a I guess the nested animated value (layouts.value[id].value) combined with the direct set is probably not "orthodox" but it works great. Also I tried with a more simple update from the hook: The bug arises if I am not compiling the lib with the plugin as my layouts are not updated on the application UI thread (stuck on an empty object Thanks for your help! |
Beta Was this translation helpful? Give feedback.
-
@tjzel Found the time to publish a demo repo to easily reproduce the issue: https://github.com/mgcrea/react-native-dnd-demo: Out-of-the-box everything works (with the default @mgcrea/react-native-dnd build compiled with the babel plugin). But if you uncomment the Hope this helps, don't hesitate to ping me if you need more info! |
Beta Was this translation helpful? Give feedback.
-
After some digging I found what is the reason for the difference in behaviour. CauseCompare the following snippets: 1. Source `DndProvider.ts`const panGesture = Gesture.Pan()
.onBegin((event) => {
const { state, x, y } = event;
debug && console.log("begin", { state, x, y });
// Gesture is globally disabled
if (disabled) {
return;
}
// console.log("begin", { state, x, y });
// Track current state for cancellation purposes
draggableState.value = state;
const { value: layouts } = draggableLayouts;
const { value: offsets } = draggableOffsets;
const { value: options } = draggableOptions;
const { value: lastActingId } = draggableActingId;
// Find the active layout key under {x, y}
const activeId = findActiveLayoutId({ x, y });
// Update shared state
draggableActingId.value = activeId;
// Check if an item was actually selected
if (activeId !== null) {
// Update activeId directly or with an optional delay
const { activationDelay } = options[activeId];
activationDelay > 0
? runOnJS(setActiveId)(activeId, activationDelay)
: (draggableActiveId.value = activeId);
// Record any ongoing current offset as our initial offset for the gesture
const activeOffset = offsets[activeId];
draggableActiveOffset.x.value = activeOffset.x.value;
draggableActiveOffset.y.value = activeOffset.y.value;
// Cancel the ongoing animation if we just reactivated the same item
if (activeId === lastActingId) {
cancelAnimation(activeOffset.x);
cancelAnimation(activeOffset.y);
// If not we should reset the resting offset to the current offset value
} else {
draggableRestingOffset.x.value = activeOffset.x.value;
draggableRestingOffset.y.value = activeOffset.y.value;
}
if (onBegin) {
const activeLayout = layouts[activeId].value;
onBegin(event, { activeId, activeLayout });
}
}
}) 2. Generated JS of `DndProvider.ts`var panGesture = _reactNativeGestureHandler.Gesture.Pan()
.onBegin(function (event) {
var state = event.state,
x = event.x,
y = event.y;
debug && console.log("begin", { state: state, x: x, y: y });
if (disabled) {
return;
}
draggableState.value = state;
var layouts = draggableLayouts.value;
var offsets = draggableOffsets.value;
var options = draggableOptions.value;
var lastActingId = draggableActingId.value;
var activeId = findActiveLayoutId({ x: x, y: y });
draggableActingId.value = activeId;
if (activeId !== null) {
var _activationDelay = options[activeId].activationDelay;
_activationDelay > 0
? (0, _reactNativeReanimated.runOnJS)(setActiveId)(activeId, _activationDelay)
: (draggableActiveId.value = activeId);
var activeOffset = offsets[activeId];
draggableActiveOffset.x.value = activeOffset.x.value;
draggableActiveOffset.y.value = activeOffset.y.value;
if (activeId === lastActingId) {
(0, _reactNativeReanimated.cancelAnimation)(activeOffset.x);
(0, _reactNativeReanimated.cancelAnimation)(activeOffset.y);
} else {
draggableRestingOffset.x.value = activeOffset.x.value;
draggableRestingOffset.y.value = activeOffset.y.value;
}
if (onBegin) {
var activeLayout = layouts[activeId].value;
onBegin(event, { activeId: activeId, activeLayout: activeLayout });
}
}
}) 3. Generated JS of pre-transpiled `DndProvider.ts`var panGesture = _reactNativeGestureHandler.Gesture.Pan()
.onBegin(
(function () {
var _e = [new global.Error(), -16, -27];
var _f = function _f(event) {
var state = event.state,
x = event.x,
y = event.y;
debug && console.log("begin", { state: state, x: x, y: y });
if (disabled) {
return;
}
draggableState.value = state;
var layouts = draggableLayouts.value;
var offsets = draggableOffsets.value;
var options = draggableOptions.value;
var lastActingId = draggableActingId.value;
var activeId = findActiveLayoutId({ x: x, y: y });
draggableActingId.value = activeId;
if (activeId !== null) {
var _activationDelay = options[activeId].activationDelay;
_activationDelay > 0
? (0, _reactNativeReanimated.runOnJS)(setActiveId)(activeId, _activationDelay)
: (draggableActiveId.value = activeId);
var activeOffset = offsets[activeId];
draggableActiveOffset.x.value = activeOffset.x.value;
draggableActiveOffset.y.value = activeOffset.y.value;
if (activeId === lastActingId) {
(0, _reactNativeReanimated.cancelAnimation)(activeOffset.x);
(0, _reactNativeReanimated.cancelAnimation)(activeOffset.y);
} else {
draggableRestingOffset.x.value = activeOffset.x.value;
draggableRestingOffset.y.value = activeOffset.y.value;
}
if (onBegin) {
var activeLayout = layouts[activeId].value;
onBegin(event, { activeId: activeId, activeLayout: activeLayout });
}
}
};
_f._closure = {
debug: debug,
disabled: disabled,
draggableState: draggableState,
draggableLayouts: draggableLayouts,
draggableOffsets: draggableOffsets,
draggableOptions: draggableOptions,
draggableActingId: draggableActingId,
findActiveLayoutId: findActiveLayoutId,
runOnJS: _reactNativeReanimated.runOnJS,
setActiveId: setActiveId,
draggableActiveId: draggableActiveId,
draggableActiveOffset: draggableActiveOffset,
cancelAnimation: _reactNativeReanimated.cancelAnimation,
draggableRestingOffset: draggableRestingOffset,
onBegin: onBegin,
};
_f.__initData = _worklet_14289401489246_init_data;
_f.__workletHash = 14289401489246;
_f.__stackDetails = _e;
return _f;
})()
) You can clearly see the distinction between 1. and 2. compared to 3. - 3. has the function workletized. const panGesture = Gesture.Pan() turned into var panGesture = _reactNativeGestureHandler.Gesture.Pan() This might seem harmless, but Reanimated Babel plugin is name sensitive. When it comes to auto-workletization of callbacks, we do it by function pattern recognition. To be more specific: Gesture.Pan().onStart(
// Callback here will be auto-workletized.
) but here: someModule.Gesture.Pan().onStart(
// Callback here will not be auto-workletized.
) because we auto-workletize Gesture Handler callbacks (and many other functions) only if We could extend this behavior to those specific functions accessed by dot notation, but it's something we don't really want to do at the moment. Going down the route of complying to various kinds of auto-generated code seems to be a bad decision. Moreover, we don't want to be too invasive with our plugin - while having SolutionAt the moment I see two solutions here.
NoteThis is an important topic for us. For some time we have been struggling with third party libraries that incorporate various parts of Reanimated into their code, later causing errors for the users and those errors would often incorrectly point to Reanimated as the offender. Unfortunately, our library is quite complex and its parts cannot be simply separated. Therefore it's of vital importance we allow more streamlined approach to the process of creating libraries that depend on Reanimated - it obviously benefits both sides. I'm very eager to hear back from you about this. Please tell me if the solution I provided actually fixes the problem. If yes, then we could create some guidelines on how to use Reanimated in React Native libraries. |
Beta Was this translation helpful? Give feedback.
-
Solution thanks to @tjzel:
You can find the relevant commit here: |
Beta Was this translation helpful? Give feedback.
Solution thanks to @tjzel:
You can find the relevant commit here:
mgcrea/react-native-dnd@785d519