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

feat: implement usePreventRemove hook #10682

Merged
merged 42 commits into from
Jul 28, 2022

Conversation

kacperkapusciak
Copy link
Member

@kacperkapusciak kacperkapusciak commented Jul 6, 2022

As we still polishing the API we're marking the hook for now as UNSTABLE_usePreventRemove.

Motivation

This PR intruduces a new hook - usePreventRemove - used for preventing going back in react-navigation. This hook would allow to support preventing of screen removal in @react-navigation/native-stack which is one of the limitations of current API.

The usePreventRemove hook would take two arguments:

  • preventRemove - boolean indicating whether to prevent screen from being removed
  • callback - optional function which is executed when screen was prevented from being removed

Example use case

function EditText({ navigation }) {
  const [text, setText] = React.useState('');
  const hasUnsavedChanges = Boolean(text);

  usePreventRemove(hasUnsavedChanges, (data) => {
    Alert.alert(
      'Discard changes?',
      'You have unsaved changes. Discard them and leave the screen?',
      [
        { text: "Don't leave", style: 'cancel', onPress: () => {} },
        {
          text: 'Discard',
          style: 'destructive',
          onPress: () => navigation.dispatch(data.action),
        },
      ]
    );
  });

  return (
    <TextInput
      value={text}
      placeholder="Type something…"
      onChangeText={setText}
    />
  );
}
Example code to test the change
import {
  NavigationContainer,
  usePreventRemove,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createStackNavigator } from '@react-navigation/stack';
import * as React from 'react';
import { Button, Text, View } from 'react-native';

function HomeScreen({ navigation }: any) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

function DetailsScreen({ navigation }: any) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Details Screen</Text>
      <Button
        title="Go to Nested"
        onPress={() => navigation.navigate('Nested')}
      />
    </View>
  );
}

function SettingsScreen({ navigation }: any) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Settings Screen</Text>
      <Text>PREVENTED FROM REMOVAL</Text>
      <Button
        title="Go to Profile"
        onPress={() => navigation.navigate('Profile')}
      />
      <Button
        title="Go back to Home"
        onPress={() => navigation.navigate('Home')}
      />
    </View>
  );
}

function ProfileScreen({ navigation }: any) {
  usePreventRemove(true);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Profile Screen</Text>
      <Button
        title="Go to Another"
        onPress={() => navigation.navigate('Another')}
      />
    </View>
  );
}

function AnotherScreen({ navigation }: any) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Another Screen</Text>
      <Button
        title="Go back to Home"
        onPress={() => navigation.navigate('Home')}
      />
    </View>
  );
}

const Stack = createNativeStackNavigator();
const NestedStack = createNativeStackNavigator();

function MyNestedStack() {
  return (
    <NestedStack.Navigator>
      <NestedStack.Screen name="Settings" component={SettingsScreen} />
      <NestedStack.Screen name="Profile" component={ProfileScreen} />
      <NestedStack.Screen name="Another" component={AnotherScreen} />
    </NestedStack.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
        <Stack.Screen name="Nested" component={MyNestedStack} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

@netlify
Copy link

netlify bot commented Jul 6, 2022

Deploy Preview for react-navigation-example ready!

Name Link
🔨 Latest commit 01cf1e7
🔍 Latest deploy log https://app.netlify.com/sites/react-navigation-example/deploys/62e2b3e055ef8c000851b4de
😎 Deploy Preview https://deploy-preview-10682--react-navigation-example.netlify.app/
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@github-actions
Copy link

github-actions bot commented Jul 6, 2022

The Expo app for the example from this branch is ready!

expo.dev/@react-navigation/react-navigation-example?release-channel=pr-10682

@kacperkapusciak kacperkapusciak linked an issue Jul 11, 2022 that may be closed by this pull request
11 tasks
@codecov-commenter
Copy link

codecov-commenter commented Jul 11, 2022

Codecov Report

Merging #10682 (c4b9bb8) into main (d19987b) will increase coverage by 0.24%.
The diff coverage is 86.84%.

@@            Coverage Diff             @@
##             main   #10682      +/-   ##
==========================================
+ Coverage   74.60%   74.85%   +0.24%     
==========================================
  Files         161      167       +6     
  Lines        5025     5122      +97     
  Branches     1962     1981      +19     
==========================================
+ Hits         3749     3834      +85     
- Misses       1239     1250      +11     
- Partials       37       38       +1     
Impacted Files Coverage Δ
.../native-stack/src/views/NativeStackView.native.tsx 66.98% <45.45%> (-1.96%) ⬇️
.../native-stack/src/utils/useDismissedRouteError.tsx 70.00% <70.00%> (ø)
...e-stack/src/utils/useInvalidPreventRemoveError.tsx 75.00% <75.00%> (ø)
packages/core/src/usePreventRemoveContext.tsx 80.00% <80.00%> (ø)
packages/core/src/PreventRemoveProvider.tsx 95.65% <95.65%> (ø)
packages/core/src/PreventRemoveContext.tsx 100.00% <100.00%> (ø)
packages/core/src/useComponent.tsx 91.66% <100.00%> (+1.66%) ⬆️
packages/core/src/useNavigationBuilder.tsx 97.34% <100.00%> (+0.01%) ⬆️
packages/core/src/usePreventRemove.tsx 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d19987b...c4b9bb8. Read the comment docs.

@kacperkapusciak kacperkapusciak marked this pull request as ready for review July 14, 2022 17:28
@kacperkapusciak kacperkapusciak merged commit 7411516 into main Jul 28, 2022
@kacperkapusciak kacperkapusciak deleted the @kacperkapusciak/implement-usePreventRemove-hook branch July 28, 2022 16:12
@GaylordP
Copy link

Thank you very much for this feature! @kacperkapusciak :)

Do you have an approximate release date in the next version @satya164 ?

Thanks you :)

@github-actions
Copy link

Hey! This issue is closed and isn't watched by the core team. You are welcome to discuss the issue with others in this thread, but if you think this issue is still valid and needs to be tracked, please open a new issue with a repro.

@hotaryuzaki
Copy link

how to use this function in class component????
is there any API has same function

@hotaryuzaki
Copy link

how to use this function in class component???? is there any API has same function

I create new component like this

import { UNSTABLE_usePreventRemove } from "@react-navigation/native";

const BackHandlerIos = ({ enable, callback }) => {
  // PREVENT SWIPE BACK, ALTERNATIVE FOR BACKHANDLER IN IOS
  UNSTABLE_usePreventRemove(enable, () => {
    callback();
  });

  return null;
}

export default BackHandlerIos;

then i called like this

constructor(props) {
  super(props);
  this.state = {
    preventRemove: true
  };
}

const _handleBackButton = () => {
    this.setState({ preventRemove: false }); // TO ENABLE BACK TO PREVIOUS SCREEN

    Alert.alert(
      `Batal Edit`,
      `Apakah Anda yakin untuk membatalkan?`,
      [
        {
          text: "OK",
          onPress: () => this.props.navigation.goBack(),
        },
        {
          text: "Batal",
          onPress: () => this.setState({ preventRemove: true }) // TO DISABLE BACK TO PREVIOUS SCREEN
        },
      ]
    );
};

render() {
  return (
    <BackHandlerIos enable={preventRemove} callback={_handleBackButton} />
  );
}

@Wenceslauu
Copy link

does it work for both android and ios?

@hotaryuzaki
Copy link

does it work for both android and ios?

This issue is iOS only, because for Android you can use BackHandler in react native with more flexible functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Preventing going back doesn't work with the native stack navigator
6 participants