Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

headerRight randomly position the component incorrectly #432

Open
lnmunhoz opened this issue Mar 21, 2020 · 77 comments · Fixed by #600 or #613
Open

headerRight randomly position the component incorrectly #432

lnmunhoz opened this issue Mar 21, 2020 · 77 comments · Fixed by #600 or #613
Labels
Area: Native Stack Bug Something isn't working

Comments

@lnmunhoz
Copy link

lnmunhoz commented Mar 21, 2020

I was testing the createNativeStackNavigation and ended up having this random issue where the component rendered by headerRight is being positioned incorrectly sometimes. There's nothing special in my code, I am just rendering a <Button> from react-native.

Here's the source code: https://github.com/lnmunhoz/react-native-experiments/blob/master/react-navigation-examples/examples/NativeNavigation.tsx.

Kapture 2020-03-21 at 20 56 50

Update

The issue also happens when the headerLargeTitle is false.

image

@WoLewicki
Copy link
Member

I cannot repro your issue either on a simulator or physical device. Upgrade packages to the newest version. Is the issue still present then? If so, please try to write which steps to take for the issue to occur.

@lnmunhoz
Copy link
Author

lnmunhoz commented Mar 25, 2020

Yes, I just tried again and the issue is still present. I did a fresh install and the issue persisted. Have you tried the repo I've sent?

The steps are simple:

  • Clone the repo
  • cd repo/react-navigation-examples
  • yarn && yarn start
  • And then open in iOS simulator

To create the stack I am using createNativeStackNavigator from react-native-screens/native-stack.

I created a snack to reproduce but also not being able to make it happen there:

The thing is, I am using a stack created by createNativeStackNavigator inside a stack from createStackNavigator.

I wanted to have parts of my app using the native stack for more simple screens where I don't need to manipulate the header too much, and others the js based stack so I can migrate incrementally.

Is that an issue or should work fine?

@WoLewicki
Copy link
Member

I cloned your project and also made a bare react-native project. The issue exists only while opening it through Expo Client. This means that the version of RNScreens in the current Expo Client is having the bug since it has RNScreens in version 2.0.0-alpha.12. Can you repro it that way to prove if it is right?

@WoLewicki
Copy link
Member

@lnmunhoz can I close it then? Is your issue solved?

@lnmunhoz
Copy link
Author

Let me try to reproduce one more time so we can figure out why it was happening.

I will update here in a couple hours.

@Deepp0925
Copy link

This is happens in bare RN project as well, so I doubt it has anything to do with Expo

@WoLewicki
Copy link
Member

@Deepp0925 does it happen with the newest RNScreens version: 2.4.0?

@Deepp0925
Copy link

Yes sir

@markmssd
Copy link

I have a workaround if it's a blocker for anyone here react-navigation/react-navigation#6746 (comment):

As a workaround I show the Icon after a few ms:

const [shouldRenderIcon, setShouldRenderIcon] = useState(false)

useEffect(() => {
const nbr = setTimeout(() => setShouldRenderIcon(true), 200)
return () => clearTimeout(nbr)
}, [])

return shouldRenderIcon ? <MyIcon /> : null

@lucasmotta
Copy link

I can also confirm that this bug still happens on react-native-screens@2.40 and react-native@0.62.

@lucasmotta
Copy link

Hey @WoLewicki, do you want me to setup a sample project, showing this issue? Here's some screenshots demonstrating the problem:

This first one, is just adding a 44x44 Touchable button as the headerRight - you can see it's quite far to the left.
Screenshot 2020-04-30 at 17 45 37

If I inspect the element, you can see it's 44x44:
Screenshot 2020-04-30 at 17 51 30

If I add a marginRight: -20, it does "fix" the alignment, but not the issue itself.
Screenshot 2020-04-30 at 17 46 22

Now if I inspect the element, you can see it's only 24px wide, so almost half of the button is cropped and not easy to tap.
Screenshot 2020-04-30 at 17 46 44

It feels like this extra 20px on the right side added by the native wrapper is causing the problem - if that space would be removed entirely the problem would be solved. The back button works this way if you inspect it:

Screenshot 2020-04-30 at 17 54 17

@sbeigel
Copy link

sbeigel commented May 2, 2020

Using a custom compontent in headerCenter is affected by the same bug/problem...

@WoLewicki WoLewicki assigned WoLewicki and unassigned WoLewicki May 8, 2020
@WoLewicki
Copy link
Member

@osdnk

@FrankFundel
Copy link

same for me

@FrankFundel
Copy link

This happens almost everytime I start the app, this really should be fixed.

@WoLewicki
Copy link
Member

I believe it is the same issue as #322. Can you try the workaround submitted there and see if it works correctly?

@AlexSmirnov9107
Copy link

same for me

@SmirnovM91
Copy link

same for me too

@immortalx
Copy link

This is happening to me also with headerCenter and headerRight only in iOS, my custom component is wrongly positioned.
It only displays correctly when the default back button is present.

@zyofeng
Copy link

zyofeng commented Jun 25, 2020

Same problem Im having

const Stack = createNativeStackNavigator();
const AccountsScreen = () => {
return <Stack.Navigator screenOptions={(props) => ({
headerRight: () => <HeaderRight {...props} />,
})}>
<Stack.Screen name="Balances" component={Balances} />
...

interface Props {
navigation?: NavigationContainerRef;
}
export const HeaderRight = (props: Props) => {
const dispatch = useDispatch();
const {navigation} = props;
return <TouchableOpacity onPress={() => dispatch(lock())}>
<Text style={{color: 'red'}}>test

}

Sometimes the Text gets pushed off screen in ios, RN .62 with latest React Navigation.
Screen Shot 2020-06-25 at 4 44 38 PM

Screen Shot 2020-06-25 at 4 43 40 PM

@ortigozamatias
Copy link

ortigozamatias commented Jun 29, 2020

Same here using Expo SDK v38, which relies on ~2.9.0.

@FrankFundel
Copy link

+1

@fukemy
Copy link

fukemy commented Feb 6, 2023

this work for me:

useLayoutEffect(() => {
      navigation.setOptions({
        ...
        },
      })
    }, [navigation])

@DavidManookyan
Copy link

DavidManookyan commented Jun 24, 2023

I know this is not the best solution but it worked for me.

headerRight: () => (
        <Pressable
          onPress={() => {
            if (chatroom && chatroom.type === 'one') return;
            navigation.navigate('InviteUsers');
          }}>
          <AddWhite
            color={chatroom && chatroom.type !== 'one' ? '#707376' : '#ffffff'}
          />
        </Pressable>
      ),

@elliotfleming
Copy link

It's been years and this still hasn't been resolved... I found this issue after having trouble with the expo-router implementation and tweaked some of the above advice...

Here's the workaround:

const [loaded, setLoaded] = useState(false)

useLayoutEffect(() => {
  setLoaded(true)
}, [])

...

headerRight: loaded ? <CustomComponent /> : undefined

@fukemy
Copy link

fukemy commented Sep 25, 2023

@elliotfleming no need to do that, just put navigation.setOptions inside uselayouteffect

@jvfalco1
Copy link

@fukemy @elliotfleming Im facing the same issue. The above solution that you guys shared unfortunately not worked

@fukemy
Copy link

fukemy commented Oct 18, 2023

@jvfalco1 post your code here and images to see the problem

@13Ksii
Copy link

13Ksii commented Oct 28, 2023

The following doesn't work. As well as useEffect. After re-loading client, the button position changes. Below is a video how it looks.

  useLayoutEffect(() => {
    const rightHeaderButton = ({ tintColor }: HeaderButtonProps) => (
      <IconButton
        name="pluscircleo"
        color={tintColor}
        onPress={() => navigation.navigate('AddPlace')}
      />
    );

    navigation.setOptions({
      headerRight: rightHeaderButton,
    });
  }, [navigation]);
Screenshare.-.2023-10-28.5_18_09.PM.mp4

@fukemy
Copy link

fukemy commented Oct 28, 2023

The following doesn't work. As well as useEffect. After re-loading client, the button position changes. Below is a video how it looks.


  useLayoutEffect(() => {

    const rightHeaderButton = ({ tintColor }: HeaderButtonProps) => (

      <IconButton

        name="pluscircleo"

        color={tintColor}

        onPress={() => navigation.navigate('AddPlace')}

      />

    );



    navigation.setOptions({

      headerRight: rightHeaderButton,

    });

  }, [navigation]);

Screenshare.-.2023-10-28.5_18_09.PM.mp4

use settimeout 100 then its will work

@13Ksii
Copy link

13Ksii commented Oct 28, 2023

indeed, the following seems to be working:

  useLayoutEffect(() => {
    const rightHeaderButton = ({ tintColor }: HeaderButtonProps) => (
      <IconButton
        name="pluscircleo"
        color={tintColor}
        onPress={() => navigation.navigate('AddPlace')}
      />
    );

    setTimeout(
      () =>
        navigation.setOptions({
          headerRight: rightHeaderButton,
        }),
      100
    );
  }, [navigation]);

@thomasttvo
Copy link

thomasttvo commented Nov 1, 2023

Still happening on 3.25.0. In my case it happens every time the width of the right component changes due to a button appearing/disappearing. The workaround for me was keeping the width consistent by setting opacity to 0/1.

@trajano
Copy link

trajano commented Dec 7, 2023

I just created a POC app I noticed this as well with the release app and it only occurs on first render of a modal but fixes itself after I close and reopen the modal
Here's the part of my code that does the button and the navigator

const Stack = createNativeStackNavigator<AuthenticatedStackParamList>();
//  HeaderButtonProps is not exposed
const CloseButton = ({ tintColor }: any) => {
  const navigation = useNavigation();
  return (<Pressable onPress={() => { navigation.goBack() }}>
    <Icon name='close' color={tintColor} />
  </Pressable>)
}

export const AuthenticatedStackNavigator = () => {
  // note on the root level the screens will take up the full area including the unsafe areas
  return (
    <Stack.Navigator screenOptions={{ headerShown: false }}>
      <Stack.Screen
        name="AuthenticatedTabs"
        component={AuthenticatedTabNavigator}
      />
      <Stack.Screen
        name="Account"
        component={AccountScreen}
        options={{
          presentation: "modal", animation: "slide_from_bottom", headerShown: true,
          headerRight: ({ tintColor }) => (<CloseButton tintColor={tintColor} />)
        }}
      />
    </Stack.Navigator>
  );
};

@trajano
Copy link

trajano commented Dec 7, 2023

One thing I tested which appears to work is that you can use 0 for the timeout so it won't be perceived. Here's the hook code I wrote that I can call on the screens that have the layout issue in my case modal with slide_from_bottom

const useHeaderRightCloseButton = () => {
  const navigation = useNavigation();
  const [shouldRenderIcon, setShouldRenderIcon] = useState(false);
  const [hitSlop, onLayout] = useHitSlop();

  //  HeaderButtonProps is not exposed
  const headerRight = useCallback(
    ({ tintColor }: any) =>
      shouldRenderIcon ? (
        <Pressable
          hitSlop={hitSlop}
          onLayout={onLayout}
          onPress={() => {
            navigation.goBack();
          }}
        >
          <Icon name="close" color={tintColor} />
        </Pressable>
      ) : null,
    [navigation, shouldRenderIcon],
  );
  useLayoutEffect(() => {
    navigation.setOptions({ headerRight });
    const h = setTimeout(() => setShouldRenderIcon(true), 0);
    return () => clearTimeout(h);
  }, [navigation, headerRight]);
};

Example usage

export const AccountScreen = ({
  navigation,
  route,
}: AuthenticatedStackScreenProps<"Account">) => {
  useHeaderRightCloseButton();
   ...

@sn-will
Copy link

sn-will commented Dec 13, 2023

Still happening on 3.25.0. In my case it happens every time the width of the right component changes due to a button appearing/disappearing. The workaround for me was keeping the width consistent by setting opacity to 0/1.

Same thing for us, the issue was happening here because we were changing the button size to add more information whenever user filtered something, keeping the same size that it was first rendered seems to solve the problem.

@tboba
Copy link
Member

tboba commented Dec 13, 2023

Hi guys! I'm sorry this still happens to you, even after so many years this issue has been created 😕
If you know how to reproduce this bug, could you create a new issue regarding it? I really want to deep dive into this problem, but since this issue is too old it will land into last pages of all issues.

@tboba
Copy link
Member

tboba commented Dec 13, 2023

(cc @sn-will @trajano @thomasttvo @13Ksii @fukemy @jvfalco1 @elliotfleming @DavidManookyan @nicastelo @FrancescoBonizzi @Johan-dutoit @jadar - bumping everyone that has posted here since 2022)

@FrancescoBonizzi
Copy link

Hi @tboba! Unfortunately I couldn't reproduce it in a deterministic way, and I ended up by making a custom header component, that way I never noticed the issue again!

@tboba
Copy link
Member

tboba commented Dec 13, 2023

@FrancescoBonizzi I understand, thanks! Even if that bug happened for you not in a deterministic way and it was still occurring quite often in the repro, could you show it?

@trajano
Copy link

trajano commented Dec 14, 2023

@tboba This is a simple two page navigator that will display the settings screen on button press. I am able to reproduce the problem in Expo Go.

https://github.com/trajano/nav-mvce

import { StatusBar } from 'expo-status-bar';
import { Pressable, View } from 'react-native';

import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { NativeStackScreenProps, createNativeStackNavigator } from "@react-navigation/native-stack";
import { Button, Icon, Text } from '@rneui/themed';
import { useCallback } from 'react';

type RootStackParamList = {
  Home: undefined;
  Settings: undefined;
};
type RootStackScreenProps<T extends keyof RootStackParamList> =
  NativeStackScreenProps<RootStackParamList, T>;
const HomeScreen = ({ navigation }: RootStackScreenProps<"Home">) => {
  const showSettings = useCallback(() => { navigation.navigate("Settings") }, [navigation]);
  return <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}><Button onPress={showSettings}>show settings</Button></View>
}

const SettingsScreen = ({ navigation }: RootStackScreenProps<"Settings">) => {
  return <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}><Text>Settings</Text></View>
}

const RootStack = createNativeStackNavigator<RootStackParamList>();
const RootNavigator = () => {
  const navigation = useNavigation();
  const headerRight = useCallback(() => (<Pressable onPress={() => { navigation.goBack() }}><Icon name="close" /></Pressable >), [navigation]);
  return (<RootStack.Navigator screenOptions={{ headerShown: false }}>
    <RootStack.Screen name="Home" component={HomeScreen} />
    <RootStack.Screen name="Settings" component={SettingsScreen}
      options={{
        presentation: "modal",
        animation: "slide_from_bottom",
        headerShown: true,
        headerRight: headerRight
      }} />
  </RootStack.Navigator>)
}

export default function App() {
  return (
    <NavigationContainer>
      <RootNavigator />
      <StatusBar style='auto' />
    </NavigationContainer>
  );
}

npx expo install @react-navigation/native @react-navigation/native-stack @rneui/themed@4.0.0-rc.8 @rneui/base@4.0.0-rc.7 @react-navigation/stack

@trajano
Copy link

trajano commented Dec 19, 2023

image

@trajano
Copy link

trajano commented Dec 19, 2023

Is that sufficient for a reproducer @tboba

@tboba
Copy link
Member

tboba commented Dec 19, 2023

@trajano this should do the thing 👍 thanks!

@trevorpfiz
Copy link

I am using expo-router and getting this bug for every headerRight. Setting a fixed width for the headerRight component is my current workaround.

@trajano
Copy link

trajano commented Dec 26, 2023

I am using expo-router and getting this bug for every headerRight. Setting a fixed width for the headerRight component is my current workaround.

This does not appear to work for me. I also tried setting a fixed height.
Did a bit more research, it may be because the main app is still on Expo 48 which contains an older version of react native

In this one https://github.com/trajano/nav-mvce/pull/1/files I tried to implement your change @trevorpfiz (one thing of note, I am not fully sure yet, but it seems you need to put the view style OUTSIDE the class.

Also I don't use setOptions

But one of the things I noticed was the X gets rendered a few milliseconds after the navigation completes showing the card but only on the first one. I suspect it is around that time where things break, also ideally the headerRight should be rendered at least once as soon as the navigation are mounted

Also @tboba shouldn't the issue be re-opened since there's a test for it now.

@geoffcfchen
Copy link

I have a workaround if it's a blocker for anyone here react-navigation/react-navigation#6746 (comment):

As a workaround I show the Icon after a few ms:

const [shouldRenderIcon, setShouldRenderIcon] = useState(false)

useEffect(() => {
const nbr = setTimeout(() => setShouldRenderIcon(true), 200)
return () => clearTimeout(nbr)
}, [])

return shouldRenderIcon ? <MyIcon /> : null

pretty sure that this solution works after I implement it correctly.

@trevorpfiz
Copy link

Turns out a fixed width doesn’t fix the issue if the header title is not static.

@tboba
Copy link
Member

tboba commented Jan 2, 2024

@trajano Yeah, I've tested your workaround and I have some comments regarding it:

  1. Could you confirm this error only occurs on Expo Go? In my case after adding react-native-screens and react-native-safe-area-context and running npx expo prebuild && yarn ios I've been able to fix it.
  2. It seems that incorrect position of the element happens only when the screen appears on the first time - after clicking show settings couple more times after I've shown modal on the first time, the element has been positioned correctly.

I'll reopen this issue since this bug still occurs, but please answer the questions I've asked above 😄

@tboba tboba reopened this Jan 2, 2024
@jp1987
Copy link

jp1987 commented Feb 8, 2024

Getting the same using Expo Router (SDK 50), none of the above work arounds fix it for me.

@trajano
Copy link

trajano commented Feb 14, 2024

@tboba I get it on the preview build on a real device when I build it on EAS at least on my app, but not on the above sample.
Your note about it being the first time is correct.

@fredrikburmester
Copy link

fredrikburmester commented Feb 29, 2024

I'm also getting this issue on Expo Router ~3.4.7 (SDK 50.0.0). It went away for me when moving more logic about the header into the layout instead of setting it on mount in the useEffect hook. Like this:

// app/(app)/_layout.tsx
<Stack.Screen
    name="settings"
    options={{
    ...
    title: "",
    headerLeft: () => (
        <View>
            ...
        </View>
        ),
    }}
/>
// app/(app)/settings.tsx
useEffect(() => {
    navigation.setOptions({
      headerRight: () => <HeaderRight onPress={() => saveMutation.mutate()} loading={saveMutation.isLoading} />,
    });
  }, [saveMutation]);

Might work for some of you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Native Stack Bug Something isn't working
Projects
None yet