Integrate strict typing for react navigation properties

In my react-native project using expo, I am incorporating typescript for better type checking.

With the use of react-navigation in my project, I can define navigationOptions on my screens and access the navigation prop.

My current focus is on strongly typing these options to receive helpful hints about available properties to set.

interface NavStateParams {
    someValue: string
}

interface Props extends NavigationScreenProps<NavStateParams> {
   color: string
}

class Screen extends React.Component<Props, any> {
    // It works as expected
    static navigationOptions: NavigationStackScreenOptions = {
        title: 'ScreenTitle'
    }
    // This does not work
    static navigationOptions: NavigationStackScreenOptions = ({navigation, screenProps }) => ({
        title: navigation.state.params.someValue
    })
}

What is the most effective approach to handling react-navigation as props for components?

Answer №1

To include NavigationType in your Props, simply follow this example:

    import { createStackNavigator, NavigationProp } from 'react-navigation';

    export interface DashboardProps {
      navigation: NavigationProp<any, any>;
    };

    export class Dashboard extends React.Component<DashboardProps, {}> {

      render() {
        return (
          <View style={styles.container}>       
            <Button
              title="Navigate to Profile"
              onPress={() => this.props.navigation.navigate('Profile')}
            />
          </View>
        );
      }
    }

Answer №2

When passing the navigation prop defined by:

let navigation = useNavigation()

To a component, the recommended way of typing is:

import {NavigationProp, ParamListBase} from '@react-navigation/native';

navigation: NavigationProp<ParamListBase>

Update:

An improved approach for robust navigation typing using the latest version of @react-navigation (6.x)

Here is a complete example:

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

type RootStackParamList = {

   ScreenOne: undefined; //current screen

   ScreenTwo: {slug: string}; // a screen we are 
// navigating to in the current screen
// where we should pass a prop named `slug` 

   ScreenThree: {data: Array<string>};

   ScreenFour: undefined; // a screen we are navigating to 
// in the current screen without passing any props
};

interface IPageProps {
   navigation: NativeStackNavigationProp<RootStackParamList, 'ScreenOne'>;
}

// Since our screen is part of the stack, there's no need 
// to use `useNavigation()` to provide the `navigation` 
// to our component, we can simply access it as a prop

function Pdp({navigation}: IPageProps) {
   return ...
}

Answer №3

An example of a simple setup using version 6.x

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

interface RouterProperties {
    navigation: NavigationProp<any, any>;
}

<TouchableOpacity onPress={() => navigation.navigate('Home')}>
    <Text>Click to Navigate Home</Text>
</TouchableOpacity>

Answer №4

Here's a successful example:

static navigationOptions = ({ navigation }: NavigationScreenProps) => ({
  ...
})

Answer №5

I encountered a similar issue and found a solution that worked for me:

import * as React from 'react'
import { NavigationScreenProps, NavigationStackScreenOptions } from 'react-navigation'

interface NavStateParams {
  someValue: string
}

// It is important to declare the type NavigationOptionsFn<TParams=any>
type NavigationOptionsFn<TParams=any> = (props: NavigationScreenProps<TParams>) => NavigationStackScreenOptions

class Screen extends React.Component {
  // This code snippet should resolve the issue
  static navigationOptions: NavigationOptionsFn<NavStateParams> = ({ navigation, screenProps }) => ({
    title: navigation.state.params.someValue
  })
}

If you want this to work globally, consider declaring the

NavigationOptionsFn<TParams>
type in a d.ts file.

Answer №6

My opinion is that using react-navigation 5.X has made things easier. Below is a guide on how to specify the type of navigation props that are passed to screens/components:

export default class Header extends React.Component<{
    navigation: StackNavigationHelpers;
}> {
...
}

Note: Verified with these versions

"@react-navigation/native": "^5.2.3",
"@react-navigation/stack": "^5.3.1",

Answer №7

 npm install --save-dev @types/jest @types/react-navigation

import { NavigationScreenProps } from "react-navigation";

export interface ILoginProps extends NavigationScreenProps<{}> { userStore: IUserStore }

export class LoginScreen extends React.Component { .... }

Answer №8

A straightforward fix

Start by including RootStackParamList type and PageProps interface in your navigator files

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import {
  createNativeStackNavigator,
  NativeStackNavigationProp,
} from '@react-navigation/native-stack';

import HomeScreen from './screens/Home';
import PasswordAddScreen from './screens/PasswordAdd';

export type RootStackParamList = {
  Home: undefined; // Define your props here
  PasswordAdd: undefined;
};

export interface PageProps<T extends keyof RootStackParamList> { // T is either Home or PasswordAdd
  navigation: NativeStackNavigationProp<RootStackParamList, T>;
}

const Stack = createNativeStackNavigator<RootStackParamList>();

function Navigator() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{ headerShown: false }}>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="PasswordAdd" component={PasswordAddScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default Navigator;

Next, add the following to your Component or Screen file

import React from 'react';
import { PageProps } from '../../Navigator';

function ItemsSeparator() {
  return <Divider my={1} bgColor="coolGray.50" />;
}

type Props = PageProps<'Home'>; // import and use the PageProps and pass the screen name - 
                                // exactly as it is in RootStackParamList

function HomeScreen({ navigation }: Props) {
  return (
    <Box safeArea bgColor="coolGray.100">
     ...
    </Box>
  );
}

export default HomeScreen;

That's it.

Answer №9

const screenOptions: NavigationScreenConfig<NavigationStackScreenOptions> = 
    ({navigation}) => ({/* Customize your navigation options here... */})

Answer №10

If you are still facing issues with extending NavigationScreenProps to properly type navigationOptions along with your custom props, here is a solution:

interface Props extends NavigationScreenProps {
  someProp: string;
  anotherProp: string;
}

export const SomeGreatScreen: NavigationScreenComponent<NavigationParams, {}, Props> = ({
  someProp,
  anotherProp,
}) => {
...
};

Using

NavigationScreenComponent<Props>
led to type errors for the destructured properties { someProp, anotherProp }. However, by specifying
NavigationScreenComponent<NavigationParams, {}, Props>
, this error was resolved. This is because the extended props type needs to be sent as the third parameter:

  export type NavigationScreenComponent<
    Params = NavigationParams,
    Options = {},
    Props = {}
  > = React.ComponentType<NavigationScreenProps<Params, Options> & Props> & {
    navigationOptions?: NavigationScreenConfig<Options>;
  };

source: react-navigation.d.ts

Answer №11

If you want to avoid manually describing all your navigation functions (e.g: navigate) in the Props' interface, you can simply extend NavigationScreenProps.

In my situation, it was necessary to prevent eslint from generating an error.

import { StackNavigator, NavigationScreenProps } from 'react-navigation';

export interface HomeScreenProps extends NavigationScreenProps {
/* add your custom props here */
};

export class HomeScreen extends React.Component<HomeScreenProps, object> {

  render() {
    return (
      <View style={styles.container}>       
        <Button
          title="Go to Details"
          onPress={() => this.props.navigation.navigate('Details')}
        />
      </View>
    );
  }
}

Answer №12

Here's a solution that appears to be effective:

private static navigationOptions: NavigationScreenOptionsGetter<
  NavigationScreenOptions
> = (nav, props) => ({
  headerTitle: nav.state.params.someValue,
});

Answer №13

When the does not work section encounters an issue due to having "strictNullChecks": true in your tsconfig.json, it results in an error. Specifically, there is an error on the line:

navigation.state.params.someValue

In this context, params is considered optional. One solution is to verify if the value was provided and offer a default option if not, as shown below:

title: navigation.state.params && navigation.state.params.someValue || 'Default title'

Answer №14

Alternatively, if you're interested in creating a custom hook and exporting it, here's how you can go about it:

import {
  NavigationProp,
  ParamListBase,
  useNavigation,
} from "@react-navigation/native";

export const useAppNavigation: () => NavigationProp<ParamListBase> = useNavigation;

To use the custom hook:

IMPORT useAppNavigation FROM your file path and then implement it as follows:

const navigation = useAppNavigation();

const onPressSignUp = () => {
  navigation.navigate("SignUp");
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

There was an issue encountered when creating the class: The parameters provided do not correspond to any valid call target signature

I am facing an issue with my code. Here is the scenario: export class MyClass { public name:string; public addr:string; constructor() {} } I have imported MyClass and trying to use it like this: import { MyClass } from './MyClass' ...

Expanding the table to display additional rows using pagination in an Angular application

I have implemented a table in Angular that displays data fetched from an API. The requirement is to initially display only the first 5 rows and provide an option for users to view more rows (in groups of 5) with pagination support. Below is the code snipp ...

Difficulties in Networking Requests Following Event Emitter Notification in an Angular Application

Within my Angular application, a network request is sent to retrieve filtered data based on user-selected filters. The function responsible for handling the filter values and executing the request is outlined as follows: public onFilterReceived(values) { ...

React Native issue: why are my variables resetting mysteriously?

Hey there, I'm currently encountering a peculiar situation involving the usage of var and React.useState() variables. I am utilizing the library https://github.com/tongyy/react-native-draggable#readme to define 2 variables - var color1 = ''; ...

What is the recommended way to define a recursive TypeScript object that consists of keys that are exclusively strings?

I am seeking to define a type for an arbitrary object with only string keys (excluding symbol) at each level of nesting. Here is what I envision (though the example provided does not work and is not valid): type RecursiveRecord = { [key: string]: ...

How to Customize Checkbox Appearance in PrimeNG

Seeking to customize the visual appearance of the label for a PrimeNG checkbox element: <div class="ui-g-7"> <p-checkbox name="group1" value="Los Angeles" label="Los Angeles" [(ngModel)]="selectedCities" inputId="la"> </p-checkbox&g ...

What could be causing my function to return as undefined the second time it's invoked?

Within my approach private onDataLoadSuccessful(users: User[], roles: Role[]) { this.alertService.stopLoadingMessage(); this.loadingIndicator = false; this.dataSource.data = users.map(data => { let newData: User; newData = ...

Using NextJS: Adding a fresh query string value to an existing asPath or modifying the current query string

Trying to wrap my head around the workings of the NextJS router system: I have articles categorized under: Medical Charity Wedding Funeral Currently, I have a navbar where users can filter articles by category and search by keyword. The category filter i ...

Retrieve a user's ID and display their posts along with comments using Angular 6

How can I retrieve all users based on their user id, iterate through them, and display all posts and comments when a specific user is clicked? You can fetch the posts from the following API: https://jsonplaceholder.typicode.com/posts And you can get thei ...

The attribute 'data' is not found within the type 'Promise<void | AxiosResponse>'

I am encountering this issue: Property 'data' is missing on type 'Promise<void | AxiosResponse<{ data: { email: string; username: string; }; }, any>>'.ts(2339) when trying to make a post request like so: const sendRequest = ...

Retrieve the value of a hidden input when a button is clicked using reactive forms in Angular

I am currently attempting to retrieve the values of hidden input fields that are dynamically added when the user clicks on the "insert more" button. If you'd like to view the code in action, you can visit this StackBlitz link: get hidden input value ...

The Nest dependency resolution is experiencing difficulties

Having trouble identifying the issue with my code. Upon reviewing the error message, everything seems fine. When running npm start in the console, the following error appears: Nest can't resolve dependencies of the DescribeService (UrlsAfipServi ...

Out of nowhere, my React App has decided to stop working despite no recent changes

Hi everyone, I recently forked a React app from https://github.com/conedex/frontend. It was working perfectly fine until it suddenly stopped with this error message: ./node_modules/@metamask/utils/node_modules/superstruct/dist/index.mjs 46:43 Module parse ...

Encountering difficulty utilizing Partial with a generic type

I'm currently working on developing a class called Map that will store mappings set by other classes, such as CharacterManager. I want the content of the map to be determined by other classes, so I have designed the Map class to accept a generic typ ...

Angular view not showing dates retrieved from MySQL database

Currently, I am experimenting with Angularjs alongside TypeScript to create a simple to-do list as a web page for practice and fun. Below is my controller that interacts with the database and stores the objects: module app.ToDo { class ToDoCtrl { ...

Generate a pre-signed URL for an AWS S3 bucket object using Typescript in NextJS, allowing for easy file downloads on the client-side using @aws-sdk/S3Client

In the utilization of v3 of the S3Client, it appears that all existing examples are based on the old aws-sdk package. The goal is for the client (browser) to access a file from S3 without revealing the key from the backend. From my research, it seems tha ...

Customizable TypeScript interface with built-in default key value types that can be easily extended

One common pattern that arises frequently in my projects involves fetching values and updating the UI based on the 'requestStatus' and other associated values. type RequestStatus = | 'pending' | 'requesting' | 'succ ...

The operation of fetching multiple documents within a transaction loop is not functioning as anticipated

I am encountering an issue where I am attempting to retrieve multiple documents within a transaction and then update them all in the same transaction (due to their interdependence). Despite following the rule of ensuring all reads occur before any writes, ...

Having trouble with inserting data into Firestore using Firebase cloud functions?

I'm attempting to insert a document from a Firebase cloud function into Firestore, but it's not functioning as expected. Here's my code snippet: import * as admin from "firebase-admin" import * as functions from "firebase-functions" admin. ...

The file that is currently being downloaded has the .pptx extension, but it is being

Take a look at this code snippet: const generateDownload = ({ link, data, title, settings })=> { const newLink = document.createElement('a'); const blobUrl = link || URL.createObjectURL(new Blob([data], settings)); newLink.setAt ...