React-spring: [react-native] Animated transform not working

Created on 11 Jan 2019  路  20Comments  路  Source: pmndrs/react-spring

I see there's #360 and #361, but it's not working for me on latest version.
Maybe there was a regression?

I'm using latest version with hooks (^7.2.8).
React Native 0.57.4 with hooks enabled.

import {View } from 'react-native'
import { animated, useTransition } from 'react-spring/hooks'

const AnimatedView = animated(View) // also tried animated(Animated.View)

This doesn't work

// ...
    from: { width: 0 },
    enter: { width: size },
    leave: { width: 0 },
// ...

<AnimatedView style={{ transform: [{ translateX: transition.props.width }] }}>

You passed an Animated.Value to a normal component

This doesn't work

// ...
    from: { transform: [{ translateX: 0 }] },
    enter: { transform: [{ translateX: size }] },
    leave: { transform: [{ translateX: 0 }] },
// ...

<AnimatedView style={transition.props}>

Invariant Violation: You must specify exactly one property per transform object

Just for reference, these work:

<AnimatedView style={{ width: transition.props.width }}>
<Animated.View style={{ transform: [{ translateX: new Animated.Value(100) }] }}>
react-native

Most helpful comment

@brunolemos @drcmda @jas99 any solution??

All 20 comments

cc @drcmda @doginthehat @Elindorath in case you have any insight

animated(View) should be correct, but i haven't had time to really care for RN for a while. Could you take a look? It's probably something really simple to fix. My local build is way ahead now, some chores i have to fix first.

any fixes for this?

Ignore the errors above because I was using react-spring/hooks instead of react-spring/native-hooks at the time I posted.

This is the error I'm currently getting when using react-spring/native-hooks:

native-hooks.js:1583 Uncaught (in promise) TypeError: this._transforms.forEach is not a function
    at AnimatedTransform.attach (native-hooks.js:1583)

The transforms variable is a AnimatedArray, so it doesn't have a .forEach. I tried to change it to transforms.getValue or transforms.getAnimatedValue, it stopped the error but didn't animate as expected. The fix is probably something like this I imagine.

@brunolemos did your recent pr fix this?

No, it's something else

ah ok i get it. the spring has a defined api, it doesn't know or care about the host be it web or react-native. to/enter/leave/from accept an object or either name:value pairs or name:[values]. (in the upcoming version also async functions and arrays with property objects). what you interpolate in there technically isn't a style, it can be anything, and how you distribute that into your view is up to you.

i guess it would be possible to apply some simple host specific transform to getValues() in the controller, but i am a little afraid that it could be too limited as people will use interpolated values anywhere, in styles, attributes, component props, children, etc.

@brunolemos @drcmda @jas99 any solution??

found a solution with interpolate, just return to whole array like so:
transform: move.interpolate(m => [{ translateX: m }])

This has worked for me:

import from native

import { animated, useSpring } from 'react-spring/native'; <----------------
import { View } from 'react-native';

const AnimatedView = animated(View);

then

  const style = useSpring({
    from: { translateY: -500 },
    to: async next => {
      await next({ translateY: 10 });
      await next({ translateY: -500, delay: 100 });
    },
  });

  return (
    <AnimatedView style={{ transform: [{ translateY: style.translateY }] }}>
      {component}
    </AnimatedView>
  );

The following worked for me on iOS, Android and Web on a react-native-web app using both react-spring 8.0.19 and 9.0.0-beta.1. I believe it should work on a react-native only app too:

import React from "react";
import { View } from "react-native";
import { animated, useSpring } from "react-spring/native";

export default () => {
  const style = {
    backgroundColor: "red",
    height: 100,
    width: 100
  };

  const motionProps = useSpring({
    translateY: 200,
    from: { translateY: 20 }
  });
  const motionStyle = {
    ...style,
    transform: [{ translateY: motionProps.translateY }]
  };

  const AnimatedView = animated(View);

  return (
    <View>
      <AnimatedView style={motionStyle} />
    </View>
  );
};

If anyone can double check this please let us know how it goes.

Can anyone confirm if v9 fixes this issue? Try with react-spring@next

The following worked for me on iOS, Android and Web on a react-native-web app using both react-spring 8.0.19 and 9.0.0-beta.1. I believe it should work on a react-native only app too:

import React from "react";
import { View } from "react-native";
import { animated, useSpring } from "react-spring/native";

export default () => {
  const style = {
    backgroundColor: "red",
    height: 100,
    width: 100
  };

  const motionProps = useSpring({
    translateY: 200,
    from: { translateY: 20 }
  });
  const motionStyle = {
    ...style,
    transform: [{ translateY: motionProps.translateY }]
  };

  const AnimatedView = animated(View);

  return (
    <View>
      <AnimatedView style={motionStyle} />
    </View>
  );
};

If anyone can double check this please let us know how it goes.

It works, but only without remote debugging disabled:(.

This works for me. Works for me with remote debugging enabled as well.

const navigation = useSpring({
        translateX: 0,
        from: { translateX: -800 },
        delay: 800
    })

return <Navigation style={{transform: [navigation]}} />

Can anyone confirm if v9 fixes this issue? Try with react-spring@next

@aleclarson It doesn't work for me.

I still get this:

TypeError: undefined is not a function (near '...this._transforms.forEach...')

This error is located at:
    in ForwardRef (at App.tsx:8)
    in Unknown (at App.tsx:37)
    in RCTView (at View.js:45)
    in View (created by Context.Consumer)
    in StyledNativeComponent (created by Styled(View))
    in Styled(View) (at App.tsx:34)
    in App (at withExpoRoot.js:20)
    in RootErrorBoundary (at withExpoRoot.js:19)
    in ExpoRootComponent (at renderApplication.js:35)
    in RCTView (at View.js:45)
    in View (at AppContainer.js:98)
    in RCTView (at View.js:45)
    in View (at AppContainer.js:115)
    in AppContainer (at renderApplication.js:34)

attach
    native.js:2816:29
addChild
    native.js:129:36
<unknown>
    native.js:184:47
attach
    native.js:184:6
addChild
    native.js:129:36
<unknown>
    native.js:184:47
attach
    native.js:184:6
AnimatedProps
    native.js:2238:4
<unknown>
    native.js:2261:48
<unknown>
    native.js:2274:16
renderWithHooks
    ReactNativeRenderer-dev.js:9473:27
updateForwardRef
    ReactNativeRenderer-dev.js:11085:6
performUnitOfWork
    ReactNativeRenderer-dev.js:17276:21
workLoop
    ReactNativeRenderer-dev.js:17316:41
renderRoot
    ReactNativeRenderer-dev.js:17417:15
performWorkOnRoot
    ReactNativeRenderer-dev.js:18423:17
performWork
    ReactNativeRenderer-dev.js:18324:24
performSyncWork
    ReactNativeRenderer-dev.js:18285:14
requestWork
    ReactNativeRenderer-dev.js:18169:19
scheduleWork
    ReactNativeRenderer-dev.js:17969:16
scheduleRootUpdate
    ReactNativeRenderer-dev.js:18642:15
render
    ReactNativeRenderer-dev.js:19512:20
renderApplication
    renderApplication.js:61:52
run
    AppRegistry.js:104:10
runApplication
    AppRegistry.js:198:26
__callFunction
    MessageQueue.js:366:47
<unknown>
    MessageQueue.js:106:26
__guard
    MessageQueue.js:314:10
callFunctionReturnFlushedQueue
    MessageQueue.js:105:17

The code that gave me this error was this:

import React, { useState, useCallback } from 'react';
import { animated, useTransition } from 'react-spring/native';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
`;

const Title = styled.Text`
  font-size: 20px;
  font-weight: 500;
  color: black;
`;

const AnimatedPage = animated(styled.View`
  width: 100%;
  height: 100%;
  justify-content: center;
  align-items: center;
`);


const pages = [
  ({ style }) => (
    <AnimatedPage style={{ ...style, backgroundColor: 'red' }}>
      <Title>A</Title>
    </AnimatedPage>
  ),
  ({ style }) => (
    <AnimatedPage style={{ ...style, backgroundColor: 'green' }}>
      <Title>B</Title>
    </AnimatedPage>
  ),
  ({ style }) => (
    <AnimatedPage style={{ ...style, backgroundColor: 'blue' }}>
      <Title>C</Title>
    </AnimatedPage>
  ),
];

export const App = () => {
  const [index, set] = useState(0);
  const onTouchStart = useCallback(() => set(state => (state + 1) % 3), []);
  const transitions = useTransition(index, p => p, {
    from: { opacity: 0, transform: 'translate3d(100%,0,0)' },
    enter: { opacity: 1, transform: 'translate3d(0%,0,0)' },
    leave: { opacity: 0, transform: 'translate3d(-50%,0,0)' },
  });

  return (
    <Container onTouchStart={onTouchStart}>
      {transitions.map(({ item, props }, key) => {
        const Page = pages[item];
        return <Page key={key} style={props} />;
      })}
    </Container>
  );
};

I don't know what shape the style prop expects transform to be? Internally, this._transforms is not an array.

Edit:

I realised my syntax might have been only for the web, so also tried this, but got another error:

export const App = () => {
  const [index, set] = useState(0);
  const onTouchStart = useCallback(() => set(state => (state + 1) % 3), []);
  const transitions = useTransition(index, p => p, {
    from: { opacity: 0, transform: [{ translate: ['100%', 0, 0] }] },
    enter: { opacity: 1, transform: [{ translate: ['0%', 0, 0] }] },
    leave: { opacity: 0, transform: [{ translate: ['-50%', 0, 0] }] },
  });

  return (
    <Container onTouchStart={onTouchStart}>
      {transitions.map(({ item, props: { opacity, transform } }, key) => {
        const Page = pages[item];
        return (
          <Page
            key={key}
            style={{
              opacity,
              transform: transform.getValue(),
            }}
          />
        );
      })}
    </Container>
  );
};

getValue() produced a [ NaN ]...

@sebinsua Hey, put it in a CodeSandbox and post the link here. Thanks! 馃憤

@aleclarson Not sure what to place into from/enter/leave since this is my first time using React Native, however the Expo project I've pushed into this repository (sebinsua/test-react-spring) is giving me the following error message:

TypeError: undefined is not a function (near '...this._transforms.forEach...')

This error is located at:
    in ForwardRef (at App.tsx:9)
    in Unknown (at App.tsx:38)
    in RCTView (at View.js:45)
    in View (created by Context.Consumer)
    in StyledNativeComponent (created by Styled(View))
    in Styled(View) (at App.tsx:35)
    in App (at withExpoRoot.js:20)
    in RootErrorBoundary (at withExpoRoot.js:19)
    in ExpoRootComponent (at renderApplication.js:35)
    in RCTView (at View.js:45)
    in View (at AppContainer.js:98)
    in RCTView (at View.js:45)
    in View (at AppContainer.js:115)
    in AppContainer (at renderApplication.js:34)

attach
    native.js:2497:29
addChild
    native.js:94:36
<unknown>
    native.js:147:81
attach
    native.js:147:40
addChild
    native.js:94:36
<unknown>
    native.js:147:81
attach
    native.js:147:40
AnimatedProps
    native.js:1994:4
<unknown>
    native.js:2015:48
<unknown>
    native.js:2023:16
renderWithHooks
    ReactNativeRenderer-dev.js:9473:27
updateForwardRef
    ReactNativeRenderer-dev.js:11085:6
performUnitOfWork
    ReactNativeRenderer-dev.js:17276:21
workLoop
    ReactNativeRenderer-dev.js:17316:41
renderRoot
    ReactNativeRenderer-dev.js:17417:15
performWorkOnRoot
    ReactNativeRenderer-dev.js:18423:17
performWork
    ReactNativeRenderer-dev.js:18324:24
performSyncWork
    ReactNativeRenderer-dev.js:18285:14
requestWork
    ReactNativeRenderer-dev.js:18169:19
scheduleWork
    ReactNativeRenderer-dev.js:17969:16
scheduleRootUpdate
    ReactNativeRenderer-dev.js:18642:15
render
    ReactNativeRenderer-dev.js:19512:20
renderApplication
    renderApplication.js:61:52
run
    AppRegistry.js:104:10
runApplication
    AppRegistry.js:198:26
__callFunction
    MessageQueue.js:366:47
<unknown>
    MessageQueue.js:106:26
__guard
    MessageQueue.js:314:10
callFunctionReturnFlushedQueue
    MessageQueue.js:105:17

It's based off this web example.

@aleclarson Is this Expo repository enough for you to see what is wrong? It could be that I'm using the API incorrectly.

Sorry if you're very busy right now. I don't know how to debug this so in the meantime I'm going to try and use a react-native-reanimated.

@sebinsua Animating arrays of objects is not supported:

  const transitions = useTransition(index, p => p, {
    from: { opacity: 0, transform: [{ translate: ['100%', 0, 0] }] },
    enter: { opacity: 1, transform: [{ translate: ['0%', 0, 0] }] },
    leave: { opacity: 0, transform: [{ translate: ['-50%', 0, 0] }] },
  });

Animate the strings (eg: -50%) instead and use them like this:

<AnimatedPage style={{ transform: [{ translateX: props.translateX }] }} />

@aleclarson Thanks! This worked when I started to use number values.

However, I have one more problem - the leave animation is not playing (the new commit in the repository demonstrates this). I was expecting the current page to move right at the same time as the new page moves from the left as worked in the example for the web (thought opposite directions). Any ideas? position: absolute fixed this!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

n1ru4l picture n1ru4l  路  3Comments

sakhisheikh picture sakhisheikh  路  3Comments

fortezhuo picture fortezhuo  路  3Comments

cmmartin picture cmmartin  路  3Comments

mkhoussid picture mkhoussid  路  3Comments