The most suitable TypeScript type for a screen being utilized again in react-navigation v5

When it comes to typing screens under react-navigation v5, I usually follow a simple pattern:

// Params definition
type RouteParamsList = {
    Screen1: {
        paramA: number
    }
    Screen2: undefined
}

// Screen1

type Props = StackScreenProps<RouteParamsList, 'Screen1'>

export const Screen1: React.FC<Props> = ...

It works perfectly for most cases.

However, I'm facing a challenge when trying to reuse the Screen1 component for different navigators:

// Params definition
type RouteParamsListShop = {
    Screen1: {
        paramA: number
    }
    Screen2: undefined
    Screen3: undefined
}

type RouteParamsListFeatures = {
    Screen1: {
        paramA: number
    }
    Screen4: undefined
}

// Screen1

type Props = StackScreenProps<RouteParamsListShop, 'Screen1'> | StackScreenProps<RouteParamsListFeatures, 'Screen1'> // Attempted solution

export const Screen1: React.FC<Props> = ...

Although using a union type allows me to extract parameters from the route correctly, the navigation method navigate poses an issue:

This expression is not callable. Each member of the union type '/* Route info here */' has signatures, but none of those signatures are compatible with each other.ts(2349)

Is there a way to properly address this typing issue, or should I consider restructuring my navigation setup to have the screen associated with only one route? Alternatively, creating two wrappers for different navigation instances may be another option.

Answer №1

If the two interfaces are identical, you can utilize this method:

interface Screen1Parameters {
  parameterA: number;
}

type ShopRouteParamsList = {
    Screen1: Screen1Parameters;
    Screen2: undefined;
    Screen3: undefined;
}

type FeaturesRouteParamsList = {
    Screen1: Screen1Parameters;
    Screen4: undefined;
}

...

type Props = StackScreenProps<ShopRouteParamsList, 'Screen1'>;

I haven't tested it yet, but using a Union should function as well, as long as the two types match:

type Props = StackScreenProps<ShopRouteParamsList, 'Screen1'> | StackScreenProps<FeaturesRouteParamsList, 'Screen1'>

Answer №2

There is a deeper issue at hand here beyond simply typing the union correctly. If our navigation prop belongs to RouteParamsListShop, then we are able to navigate to Screen2. However, if our navigation prop is for RouteParamsListFeatures, then we cannot navigate to Screen2 since it is not defined on that stack. This leads to an inherent compatibility issue.

Are you only attempting to navigate from Screen1 to another screen that is declared on both stacks? If so, we can handle this with proper typing. If not, there may be a design flaw present.

Instead of using a union, what you really need is a shared subset that includes both the target screen and the current screen. By implementing some helper types, we can simplify this process.

type SharedKeys = keyof RouteParamsListShop & keyof RouteParamsListFeatures

// We pick from both to account for potential differences in params
type SharedParams = Pick<RouteParamsListShop, SharedKeys> & Pick<RouteParamsListFeatures, SharedKeys>

type Props = StackScreenProps<SharedParams, 'Screen1'>

This setup enables you to utilize navigation.navigate - but restricts navigation to screens defined in both stack types. In the provided example, there are no valid screens left to navigate to, but presumably, there will be applicable navigation targets in your real-world scenario.

Experience TypeScript Playground

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

Template URI parameters are being used in a router call

Utilizing the useRouter hook in my current project. Incorporating templated pages throughout the application. Implementing a useEffect hook that responds to changes in the router and makes an API call. Attempting to forward the entire URL to /api/${path ...

Tips for enforcing a mandatory type with TypeScript

Creating a custom type called InputType with optional properties like this: export type InputType = { configJsonUrl?: string; configJsObject?: DataType; rawData?: { [key: string]: string }; action?: () => void; }; export type DataType = { id ...

Why is the imported package not being recognized in the namespace declaration of my Node.js TypeScript file?

Currently, I am utilizing the WebStorm IDE developed by JetBrains to modify a TypeScript file within a Node.js v8.6.0 project. The JavaScript version set for this project is JSX Harmony. In the beginning of the TypeScript source file, there is an import st ...

Creating seamless transitions between pages using hyperlinks

On the homepage, there are cards that list various policies with a "details" button above them. Clicking on this button should take me to the specific details page for that policy. However, each product can only have one type assigned to it. For instance: ...

Create an array of arrays within a loop using TypeScript

My application contains an object with dates and corresponding time arrays. The console log output is displayed below: 32: { 1514160000: Array [ 1200, 1500 ], 1514764800: Array [ 1200, 1500 ], 1515369600: Array [ 1200, 1500 ], 1515974400: Array [ 700, 12 ...

What are the benefits of adding member functions to the data structures of React.js store?

Using React.js and Typescript, I store plain Javascript objects in the React.js store. These objects are sometimes received from the server without any member functions, but I wish to add functions for better organization. Instead of having to rely on exte ...

Exploring the capabilities of the hardware camera in Angular 2

Struggling to implement the tutorial in Angular2. The challenge lies in making navigator.mediaDevices.getUserMedia function properly. The error message indicates that mediaDevices is not recognized on type 'navigator'. Refer to the media capture ...

Output Scalable Vector Graphics (SVG) content on a webpage

I need to include an SVG element in my Angular 2+ code. My goal is to provide users with the option to print the SVG element as it appears on the screen. <div class="floor-plan" id="printSectionId2" (drop)="onDrop($event)" (dragover)="onDragOver ...

Utilize an API call to fetch data and seamlessly showcase it on the webpage using Vue.js

I have created an API and defined it in my TypeScript file const Book = { async getBookType(intID: string): Promise<Book[]> { // const intId = API.Product.getCurrentId(); const intId = ''; const response = await h ...

Tips for adjusting the position of overflowing text on a website using CSS in real-time

I'm currently working on an Angular application and I'd like to customize the styling so that any text exceeding 128 characters is not displayed. Ideally, if the text exceeds 128 characters, it should move to the left; otherwise, it should remain ...

Angular 8 combined with Mmenu light JS

Looking for guidance on integrating the Mmenu light JS plugin into an Angular 8 project. Wondering where to incorporate the 'mmenu-light.js' code. Any insights or advice would be greatly appreciated. Thank you! ...

Employing Multer and Express in conjunction with TypeScript

Overview Currently, I am working on a project that involves creating a user-friendly website where individuals can easily upload images. For this particular task, I have employed Node.js, React, Multer, and Typescript. Issue at Hand app.post('/admi ...

Make TypeScript parameter optional if it is not supplied

I am working with an interface that defines scenes and their parameters: export interface IScene<R extends string> { path: R; params?: SceneParams; } The SceneParams interface looks like this: export interface SceneParams { [key: string]: s ...

Reduce the use of if statements

Is there a way to optimize this function by reducing the number of if statements? The currentFeatures are determined by a slider in another file. The cost is updated if the currentFeatures do not match the previousFeatures, and if the user changes it back ...

The functionality of ngModel seems to be malfunctioning when used within select options that are generated inside

I'm currently working on dynamically adding options to an HTML select element within a for loop. I am using [(ngModel)] to get the selected option, but initially no option is pre-selected. Here's a snippet of the code: <table align="center"& ...

What steps are required to transform a TypeScript class with decorators into a proper Vue component?

When I inquire about the inner workings of vue-class-component, it seems that my question is often deemed too broad. Despite examining the source code, I still struggle to grasp its functionality and feel the need to simplify my understanding. Consider th ...

Upgrading to Angular 14 has caused issues with component testing that involves injecting MAT_DIALOG_DATA

After upgrading Angular from 14.1.1 to 14.2.10 and Material from 14.1.1 to 14.2.7, a peculiar issue arose when running tests with the default Karma runner. I am at a loss as to what could have caused this problem, so any advice would be appreciated. The u ...

Master the art of properly switching on reducer-style payloads in Typescript

Currently, I am dealing with two types of data: GenArtWorkerMsg and VehicleWorkerMsg. Despite having a unique type property on the payload, my Searcher is unable to differentiate between these data-sets when passed in. How can I make it understand and dis ...

Angular Pipe -- Implicit type 'any' error occurs when trying to index type 'string' with an expression

Encountering an error while constructing the time ago pipe: Obtaining an implicit 'any' type due to inability to index type with a 'string' expression if (value) { const seconds = Math.floor( (+new Date() - +new Date(Numb ...

Why does the method of type assigning vary between actual and generic types?

There are no errors in the code shown below: type C = {b: string}; class Class { data: C; constructor(data: C) { this.data = data; } test() { const hack: C & {a?: any} = this.data; //no error } } However, when a g ...