react-native-shared-element icon indicating copy to clipboard operation
react-native-shared-element copied to clipboard

Error while updating property 'endNode' of a view managed by: RNSharedElementTranstion

Open julianfrancodev opened this issue 4 years ago • 19 comments

I'm using react-navigation-shared-element@next react-native-shared-element and @react-navigation/native@^5.0.9 @react-navigation/stack@^5.1.1

Screenshot_20200524_232842

I have that error when execute more then 2 times an shared element animation.

Screenshot_20200524-232237_shop

Thanks <3

julianfrancodev avatar May 25 '20 04:05 julianfrancodev

Duplicate of #30

IjzerenHein avatar May 25 '20 07:05 IjzerenHein

Which version of react-native and react-native-shared-element are you using? And on what Android phone (API) are you seeing this?

IjzerenHein avatar May 25 '20 07:05 IjzerenHein

Same here with a Xioami Mi A1 and

Library Version
react-native 0.61.5
react-navigation 4.3.9
react-navigation-shared-element 2.3.0
react-navigation-stack 2.5.1
react-native-shared-element 0.7.0

jorgemasta avatar May 25 '20 11:05 jorgemasta

Which version of react-native and react-native-shared-element are you using? And on what Android phone (API) are you seeing this?

Library Version
react-native 0.62.2
react-navigation-shared-element@next ^5.0.0-alpha1
react-native-shared-element ^0.7.0

I'm seeing this in Note 10+ API Level 29 and API level 28

julianfrancodev avatar May 25 '20 17:05 julianfrancodev

I have noticed that I have this problem when I use a SharedElement inside a ListHeaderComponent of a FlatList. Only android.

jorgemasta avatar May 28 '20 17:05 jorgemasta

@jorgemasta Thanks for sharing that, that might help 👍

IjzerenHein avatar May 29 '20 06:05 IjzerenHein

I would be awesome if perhaps someone can add a reproduction case to the ./test-app, that would be really helpful to me and would speed fixing this up 👍

IjzerenHein avatar May 29 '20 06:05 IjzerenHein

i ran into this issue as well, and found it was not the child view that was missing but instead it was the ancestor; after setting collaspable={false} on the ancestor view where my ref was, this issue went away.

ironforward avatar Jun 15 '20 19:06 ironforward

I've encountered this problem when the shared element is in a list thats in a tabNavigator thats in a stack navigator

<Stack_Navigator>
	
	<ViewItemPage>
	
	</ViewItemPage>
	
	<TabNavigator>
		
		<Screen1>

			<List>

				<MySharedElement />

			</List>

		</Screen1>

		<Screen2>

		</Screen2>

	</TabNavigator

</Stack_Navigator>

debugged the call method and saw that otherRoute from createSharedStackNavigator configuration is from TabNavigator page and not the Screen1 page (where is should be called from). the error may be thrown because after the target page is mounted, it cannot find the sharedElement to navigate back to because its not on the TabNavigator page but in the list.

when the sharedElement is not in a nested tabNavigator, works fine

willroyMyles avatar Jul 11 '20 22:07 willroyMyles

I've encountered a similar error, even when I removed the elements out of the nested navigator Screenshot_20200912-195021

rakhi2104 avatar Sep 12 '20 14:09 rakhi2104

+1 i met the same issue when try it with react navigation v5.

ducpt2 avatar Oct 15 '20 04:10 ducpt2

hi @ironforward , i am using react-navigation-share-element 5.0.0-alpha1 and met this issue, where i can add collaspable={false} to fix the issue. Thanks

ducpt2 avatar Oct 15 '20 06:10 ducpt2

Hi, is there any news about this? Unfortunately I met the same issue with 5.0.0-alpha1 and collapsable={false} on the ancestor View doesn't solve it. Thanks!

flaviolivolsi avatar Nov 06 '20 17:11 flaviolivolsi

bump.. also experiencing this issue on production

iduuck avatar Jan 02 '21 18:01 iduuck

Any info about this still happening... on 5.0.0-alpha1

macist-m avatar Jan 27 '21 22:01 macist-m

any updates on this?

robertohein avatar Mar 29 '21 19:03 robertohein

Maybe this will fix the isssue, I extended createSharedElementStackNavigator.tsx from react-navigation-shared-element v5.0.0-alpha1:

import * as React from 'react';
import {
  useNavigationBuilder,
  createNavigatorFactory,
  StackRouter,
  DefaultNavigatorOptions,
  RouteConfig,
  StackRouterOptions,
  StackNavigationState,
} from '@react-navigation/native';
import {
  CardAnimationContext,
  StackView,
  StackNavigationOptions,
} from '@react-navigation/stack';
import {SharedElementRendererProxy} from 'react-navigation-shared-element/lib/module/SharedElementRendererProxy';
import SharedElementRendererContext from 'react-navigation-shared-element/lib/module/SharedElementRendererContext';
import SharedElementRendererView from 'react-navigation-shared-element/lib/module/SharedElementRendererView';
import SharedElementRendererData from 'react-navigation-shared-element/lib/module/SharedElementRendererData';
import createSharedElementScene from 'react-navigation-shared-element/lib/module/createSharedElementScene';
import {
  SharedElementSceneComponent,
  SharedElementsComponentConfig,
} from 'react-navigation-shared-element/lib/module/types';
import {
  StackNavigationConfig,
  StackNavigationEventMap,
} from '@react-navigation/stack/lib/typescript/src/types';
import { Route } from '@react-navigation/native';

let _navigatorId = 1;

export default function createSharedElementStackNavigator<
  ParamList extends Record<string, object | undefined>
>(options?: { name?: string }) {
  const navigatorId =
    options && options.name ? options.name : `stack${_navigatorId}`;
  _navigatorId++;

  const rendererDataProxy = new SharedElementRendererProxy();

  type Props = DefaultNavigatorOptions<StackNavigationOptions> &
    StackRouterOptions &
    StackNavigationConfig;

  function SharedElementStackNavigator({
    initialRouteName,
    children,
    screenOptions,
    ...rest
  }: Props) {
    const { state, descriptors, navigation } = useNavigationBuilder<
      StackNavigationState,
      StackRouterOptions,
      StackNavigationOptions,
      StackNavigationEventMap
    >(StackRouter, {
      initialRouteName,
      children,
      screenOptions,
    });

    const rendererDataRef = React.useRef<SharedElementRendererData | null>(
      null
    );
    
    const [shouldShowTransition, setShouldShowTransition] = React.useState(false);
    React.useEffect(() => {
      setShouldShowTransition(true);
    }, [state]);

    function handleTransitionEnd({ route }: { route: Route<string> }, closing) {
      setShouldShowTransition(false);
      navigation.emit({
        type: 'transitionEnd',
        data: {closing},
        target: route.key,
      });
    };

    return (
      <SharedElementRendererContext.Consumer>
        {rendererData => {
          // In case a renderer is already present higher up in the chain
          // then don't bother creating a renderer here, but use that one instead
          if (!rendererData) {
            rendererDataRef.current =
              rendererDataRef.current || new SharedElementRendererData();
            rendererDataProxy.source = rendererDataRef.current;
          } else {
            rendererDataProxy.source = rendererData;
          }
          return (
            <SharedElementRendererContext.Provider value={rendererDataProxy}>
              <StackView
                {...rest}
                state={state}
                navigation={navigation}
                descriptors={descriptors}
                onTransitionEnd={handleTransitionEnd}
              />
              {rendererDataRef.current && shouldShowTransition ? (
                <SharedElementRendererView
                  rendererData={rendererDataRef.current}
                />
              ) : (
                undefined
              )}
            </SharedElementRendererContext.Provider>
          );
        }}
      </SharedElementRendererContext.Consumer>
    );
  }

  const navigatorFactory = createNavigatorFactory<
    StackNavigationState,
    StackNavigationOptions,
    StackNavigationEventMap,
    typeof SharedElementStackNavigator
  >(SharedElementStackNavigator);

  const { Navigator, Screen } = navigatorFactory<ParamList>();

  type ScreenProps<RouteName extends keyof ParamList> = Omit<
    RouteConfig<
      ParamList,
      RouteName,
      StackNavigationState,
      StackNavigationOptions,
      StackNavigationEventMap
    >,
    'component' | 'children'
  > & {
    component: SharedElementSceneComponent;
    sharedElementsConfig?: SharedElementsComponentConfig;
  };

  function wrapComponent(component: SharedElementSceneComponent) {
    return createSharedElementScene(
      component,
      rendererDataProxy,
      CardAnimationContext,
      navigatorId
    );
  }

  // Wrapping Screen to explicitly statically type a "Shared Element" Screen.
  function wrapScreen<RouteName extends keyof ParamList>(
    _: ScreenProps<RouteName>
  ) {
    return null;
  }

  type NavigatorProps = React.ComponentProps<typeof Navigator>;

  function getSharedElementsChildrenProps(children: React.ReactNode) {
    return React.Children.toArray(children).reduce<any[]>((acc, child) => {
      if (React.isValidElement(child)) {
        if (child.type === wrapScreen) {
          acc.push(child.props);
        }

        if (child.type === React.Fragment) {
          acc.push(...getSharedElementsChildrenProps(child.props.children));
        }
      }
      return acc;
    }, []);
  }

  // react-navigation only allows the Screen component as direct children
  // of Navigator, this is why we need to wrap the Navigator
  function WrapNavigator(props: NavigatorProps) {
    const { children, ...rest } = props;
    const componentMapRef = React.useRef<Map<any, any>>(new Map());
    const screenChildrenProps = getSharedElementsChildrenProps(children);

    return (
      <Navigator {...rest}>
        {screenChildrenProps.map(
          ({ component, sharedElementsConfig, ...childrenProps }) => {
            if (sharedElementsConfig)
              component.sharedElements = sharedElementsConfig;

            if (!componentMapRef.current.has(component)) {
              componentMapRef.current.set(component, wrapComponent(component));
            }

            const wrappedComponent = componentMapRef.current.get(component);

            return (
              <Screen
                key={childrenProps.name}
                {...childrenProps}
                component={wrappedComponent}
              />
            );
          }
        )}
      </Navigator>
    );
  }

  return {
    Navigator: WrapNavigator,
    Screen: wrapScreen,
  };
}

I added code below to createSharedElementStackNavigator.tsx:

import { Route } from '@react-navigation/native';

...

const [shouldShowTransition, setShouldShowTransition] = React.useState(false);
React.useEffect(() => {
  setShouldShowTransition(true);
}, [state]);

function handleTransitionEnd({ route }: { route: Route<string> }, closing) {
  setShouldShowTransition(false);
  navigation.emit({
    type: 'transitionEnd',
    data: {closing},
    target: route.key,
  });
};

...

<SharedElementRendererContext.Provider value={rendererDataProxy}>
  <StackView
    {...rest}
    state={state}
    navigation={navigation}
    descriptors={descriptors}
    onTransitionEnd={handleTransitionEnd}
  />
  {rendererDataRef.current && shouldShowTransition ? (
    <SharedElementRendererView
      rendererData={rendererDataRef.current}
    />
  ) : (
    undefined
  )}
</SharedElementRendererContext.Provider>

Basically, I tried to make sure the node is already created when RNSharedElementTranstion trying to resolve the node or view. I do this by mounting SharedElementRendererView (which contains SharedElementTransition) after react navigation state is changed, and then unmount it after screen transition has ended, so on the next state change, it in unmounted state. It seems fix the issue, I already tested it on Samsung Galaxy Tab A 8.0 (2019) with android version 9.

HanifAnnurRahman avatar Apr 16 '21 06:04 HanifAnnurRahman

Same issue here in debug mode on Android. Is not happening always.

LaurenceM10 avatar May 30 '21 07:05 LaurenceM10

Hello fellow android users, I too experienced crashes etc. HOWEVER, I don't think it was due to this library. This library works fine. The problem is when you use this library with components that can't manage their states properly on the native side lol. For example, most of you use Reanimated2's Animated.View, try replacing that Animated.View with View from the react-native library and watch as your crashes disappear. In fact, for animations in components that use SharedElement you can just use the good ol' Animated component from the react-native library as well. This is what I have done. Having said that, I still use Reaniamted2 for everything else, it's just when I'm using SharedElement, I'll avoid it.

Hope this helps, bye.

gigadeplex avatar Aug 23 '21 13:08 gigadeplex

Thank you @gigadeplex for your comment. Given how long this issue has been inactive I'm closing it as stale.

p-syche avatar Jan 26 '23 12:01 p-syche