I'm trying to use a SharedValue from react-native-reanimated2 to store an object (box positions). The object is of the following shape:
boxPositions.value = {
[boxId]: {x: 0, y: 0}
...
}
My goal is to update the object in the SharedValue so that it animates a specific box's position given its id
.
To update a box's position, I'm doing the following, and applying withSpring()
to the object's y
property to have it animate:
const moveRandomBox = () => {
const randId = Math.floor(Math.random() * boxes.length) + 1;
boxPositions.value = {
...boxPositions.value,
[randId]: {
x: boxPositions.value[randId].x,
y: withSpring(boxPositions.value[randId].y - 50)
}
};
}
I'm then passing this shared value object to my box component itself, and then creating an animated style to transform the box's position:
const rStyle = useAnimatedStyle(() => {
const boxPosition = boxPositions.value[box.id] ? ? {x: 0, y: 0};
return {
transform: [
{translateX: boxPosition.x},
{translateY: boxPosition.y} // this value should be animated
]
};
}, []);
The problem is that this approach of applying the withSpring()
animation causes the box to not move at all when moveRandomBox()
is called.
However, if I change the moveRandomBox()
function to set the y
position to a plain value:
[randId]: {
x: boxPositions.value[randId].x,
y: boxPositions.value[randId].y - 50 // instead of: withSpring(boxPositions.value[randId].y - 50)
}
and then change the useAnimatedStyle()
hook to use the withSpring()
animation method, it works as expected:
return {
transform: [
{translateX: boxPosition.x},
{translateY: withSpring(boxPosition.y)} // instead of just: boxPosition.y
]
};
In my actual code I don't want to actually do this, as I have different places where I set my box's x
and y
positions, and I want the animation to be different and not always the same. So I want the animation to be set when I update the box's position (like in the first appraoch), and not in the style definition (second example).
My question is: Why does the first approach of setting the animation not work, whereas the second does? What is wrong with the first approach that stops it from working? How would I go about making the first approach work? I only seem to experience this issue when the SharedValue is an object, setting a SharedValue to an individual withSpring()
value seems to work correctly.
Runnable Example:
To reproduce, change the preview to iOS. Once loaded, click the button "Move random box", and you'll see none of the boxes move. If you change the code to call withSpring()
in the useAnimatedStyle()
hook like above and remove the withSpring()
call from the moveRandomBox()
method you'll see that the box does move.
Based on your comment, what you want is to actually two issues.
For the first issue, we've already found a solution. Instead of springifying the full object (which is not possible because of how reanimated work), you need to specify the x
and y
values seperatly. I'm not really into the details of how a shared value works, but it must have to be something between the two threads and having object references, where children changes are not recognized because of the way they are handled / passed along.
Your second issue is a totally different issue, where you want to update a single components state. In this case to move a random box. For this you need to have the box components be reusable. So their movement logic should stay inside the component itself, and not be brought up to the parent state.
For example when using a ScrollView
, you're not lifting all their positions state up into the parent, but instead assign a ref
which holds the functions you want to call. E.g. scrollTo
.
This is the same for your box components. They should own their own behaviour, so that you can assign a ref
to them and randomly get one of those refs to call the function you need. E.g. moveBoxTo()
.
If this is not what you want, you could give each box an unique id. Something like you currently have. Since your 'random' movement seems to be static, (so always do the same). You could pass an 'id-to-move' to all the boxes and listen to that value using an useAnimatedReaction
. In this hook you check if the id of the box matches the random id that should move, and animate its position to the expected point.