0

I'm trying to re-use keys from a type object in another object with it's own type. I've tried many different techniques, but can't find anything that clicks right. Here is what I have...

github repo: https://github.com/FiddleFreakStackOverflow/k-in-keyof-object-is-string-and-not-specific/blob/main/app/Problem.tsx

The code below can be copied into a .tsx file for review on the typescript errors:

(assuming of course you have the node_modules installed that come with the import packages)

import React, { ReactNode } from 'react';
import { Text, View } from 'react-native';
import type {
  DefaultNavigatorOptions,
  ParamListBase,
  StackNavigationState,
  StackRouterOptions,
  TypedNavigator
} from '@react-navigation/native';
import type { NativeStackNavigationEventMap, NativeStackNavigationOptions } from '@react-navigation/native-stack';
import type { NativeStackNavigationConfig } from '@react-navigation/native-stack/lib/typescript/src/types';

type NavigationStackProps = DefaultNavigatorOptions<
  ParamListBase,
  StackNavigationState<ParamListBase>,
  NativeStackNavigationOptions,
  NativeStackNavigationEventMap
> &
  StackRouterOptions &
  NativeStackNavigationConfig;

type NavigationStack = TypedNavigator<
  StackParamsList,
  StackNavigationState<ParamListBase>,
  NativeStackNavigationOptions,
  NativeStackNavigationEventMap,
  ({ id, initialRouteName, children, screenListeners, screenOptions, ...rest }: NavigationStackProps) => JSX.Element
>;

type StackParamsList = {
  dashboard: undefined;
  'hamburger-list-modal': undefined;
  'inventory-main': undefined;
  'inventory-product': undefined;
  loading: undefined;
  login: undefined;
  settings: undefined;
  'settings-initial': undefined;
};

type StackGroupType = 'main' | 'loading' | 'auth' | 'settingsInitial';

type ScreenSelection = { route: keyof StackParamsList; screenTitle: string; stackGroupType: StackGroupType };

interface MainScreenSelections {
  dashboard: ScreenSelection;
  hamburgerListModal: ScreenSelection;
  inventoryMain: ScreenSelection;
  inventoryProduct: ScreenSelection;
  loading: ScreenSelection;
  login: ScreenSelection;
  settings: ScreenSelection;
  settingsInitial: ScreenSelection;
}

type Screens = Partial<{
  [K in keyof StackParamsList]: { screen: JSX.Element; screenTitle: string; stackGroupType: StackGroupType };
}>;

const mainScreenSelections: MainScreenSelections = {
  dashboard: { route: 'dashboard', screenTitle: 'Dashboard', stackGroupType: 'main' },
  hamburgerListModal: { route: 'hamburger-list-modal', screenTitle: '', stackGroupType: 'main' },
  inventoryMain: { route: 'inventory-main', screenTitle: 'Inventory', stackGroupType: 'main' },
  inventoryProduct: { route: 'inventory-product', screenTitle: 'Inventory', stackGroupType: 'main' },
  loading: { route: 'loading', screenTitle: '', stackGroupType: 'loading' },
  login: { route: 'login', screenTitle: 'Login', stackGroupType: 'auth' },
  settings: { route: 'settings', screenTitle: 'Settings', stackGroupType: 'main' },
  settingsInitial: { route: 'settings-initial', screenTitle: 'Settings', stackGroupType: 'settingsInitial' }
};

const defaultHome: ScreenSelection = {
  route: mainScreenSelections.dashboard.route,
  screenTitle: mainScreenSelections.dashboard.screenTitle,
  stackGroupType: mainScreenSelections.dashboard.stackGroupType
};

function Dashboard(): JSX.Element {
  return (
    <View>
      <Text>Dashboard</Text>
    </View>
  );
}

function Settings(): JSX.Element {
  return (
    <View>
      <Text>Settings</Text>
    </View>
  );
}

function InventoryMain(): JSX.Element {
  return (
    <View>
      <Text>InventoryMain</Text>
    </View>
  );
}

function InventoryProduct(): JSX.Element {
  return (
    <View>
      <Text>InventoryProduct</Text>
    </View>
  );
}

const screens: Screens = {
  [mainScreenSelections.dashboard.route]: {
    screen: Dashboard,
    screenTitle: mainScreenSelections.dashboard.screenTitle,
    stackGroupType: mainScreenSelections.dashboard.stackGroupType
  },
  [mainScreenSelections.settings.route]: {
    screen: Settings,
    screenTitle: mainScreenSelections.settings.screenTitle,
    stackGroupType: mainScreenSelections.settings.stackGroupType
  },
  [mainScreenSelections.inventoryMain.route]: {
    screen: InventoryMain,
    screenTitle: mainScreenSelections.inventoryMain.screenTitle,
    stackGroupType: mainScreenSelections.inventoryMain.stackGroupType
  },
  [mainScreenSelections.inventoryProduct.route]: {
    screen: InventoryProduct,
    screenTitle: mainScreenSelections.inventoryProduct.screenTitle,
    stackGroupType: mainScreenSelections.inventoryProduct.stackGroupType
  }
};

function getScreensWithoutHome(Stack: NavigationStack, defaultHome: ScreenSelection) {
  const screensWithoutHome: Screens = Object.entries(screens).filter((key, value) => key !== defaultHome.route);
}

function GroupMain(Stack: NavigationStack): ReactNode {
  return (
    <Stack.Group>
      <Stack.Screen
        name={defaultHome.route}
        component={screens[defaultHome.route].screen}
        options={{ title: defaultHome.route }}
      />
      <Stack.Screen
        name={mainScreenSelections.settings.route}
        component={Settings}
        options={{
          title: mainScreenSelections.settings.screenTitle
        }}
      />
      <Stack.Screen
        name={mainScreenSelections.inventoryMain.route}
        component={InventoryMain}
        options={{
          title: mainScreenSelections.inventoryMain.screenTitle
        }}
      />
      <Stack.Screen
        name={mainScreenSelections.inventoryProduct.route}
        component={InventoryProduct}
        options={{
          title: mainScreenSelections.inventoryProduct.screenTitle
        }}
      />
    </Stack.Group>
  );
}

These are the typescript errors I'm seeing:

enter image description here

Fiddle Freak
  • 1,923
  • 5
  • 43
  • 83
  • 2
    Screenshots of IDEs are unfortunately not appropriate here. [Please replace images of code/logs/errors/tooltips with plaintext versions.](https://meta.stackoverflow.com/a/285557/2887218) – jcalz Aug 10 '23 at 22:55
  • Relevant: https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript – Alex Wayne Aug 10 '23 at 23:40

1 Answers1

0

The first issue in the screenshot was typescript did not recognize the key because when casting to an object, the key automatically gets re-assigned with the type string. We can get around this error by changing the following:

from...

const screensWithoutHome: Screens = Object.entries(screens).filter((key, value) => key !== defaultHome.route);

to...

const screensWithoutHome: Screens = Object.fromEntries(
  Object.entries(screens).filter(([key, value]) => key !== defaultHome.route)
);

The second issue was due to not all keys being utilized and thus could possibly be undefined. So we can get arround this be using the satisfies in typescript as follows:

const screens = {
  [mainScreenSelections.dashboard.route]: {
    screen: Dashboard,
    screenTitle: mainScreenSelections.dashboard.screenTitle,
    stackGroupType: mainScreenSelections.dashboard.stackGroupType
  },
  [mainScreenSelections.settings.route]: {
    screen: Settings,
    screenTitle: mainScreenSelections.settings.screenTitle,
    stackGroupType: mainScreenSelections.settings.stackGroupType
  },
  [mainScreenSelections.inventoryMain.route]: {
    screen: InventoryMain,
    screenTitle: mainScreenSelections.inventoryMain.screenTitle,
    stackGroupType: mainScreenSelections.inventoryMain.stackGroupType
  },
  [mainScreenSelections.inventoryProduct.route]: {
    screen: InventoryProduct,
    screenTitle: mainScreenSelections.inventoryProduct.screenTitle,
    stackGroupType: mainScreenSelections.inventoryProduct.stackGroupType
  }
} satisfies Screens;

I created a solution file with no typescript errors here: https://github.com/FiddleFreakStackOverflow/k-in-keyof-object-is-string-and-not-specific/blob/main/app/Solution.tsx

Fiddle Freak
  • 1,923
  • 5
  • 43
  • 83