react-navigation icon indicating copy to clipboard operation
react-navigation copied to clipboard

Typescript support for Themes

Open nimabrownlee opened this issue 5 years ago • 16 comments

Current Behavior

I am adding extra colors to the Theme but Typescript complains that they don't exist when using the useTheme hook.

const MyTheme = {
  ...DefaultTheme,
  colors: {
    ...DefaultTheme.colors,
    primary: '#009ADA',
    secondary: 'red',
  },
}

Expected Behavior

Typescript should detect proper types.

How to reproduce

Add the above theme.

Your Environment

software version
iOS or Android
@react-navigation/native 5.8.10
react-native 0.63.4
expo 40.0.0
node 12+
npm or yarn yarn

nimabrownlee avatar Dec 18 '20 12:12 nimabrownlee

I tried to redeclare the theme in seprate global.d.ts file but that doesnt work either:

export declare type Theme = {
  dark: boolean;
  colors: {
    primary: string;
    background: string;
    card: string;
    text: string;
    border: string;
    notification: string;
    testColor: string;
  };
};

huzaifaaak avatar Dec 21 '20 06:12 huzaifaaak

I have successfully extended the theme by using an interface like this one:

export interface CustomTheme extends Theme {
  custom: {
    colors: {
      customColor: string
    }
  }

But now as I think about it, a type like this one might work just the way you want it to, by extending the existing colors property:

type CustomTheme = {
  colors: {
    customColor: string
  }
} & Theme

Hope these help.

KurtMar avatar Jan 01 '21 19:01 KurtMar

How do you use the costume theme then? When I am trying to use it I get a message, that Type 'Theme' is not assignable to type 'CustomTheme'. Property 'appColors' is missing in type 'Theme' but required in type '{ colors: { button: string; placeholder: string; }; appColors: ... }

I declare it like that in my theme file:

export type CustomTheme = {
  colors: {
    button: string;
    placeholder: string;
  };
  appColors: {
    primary: string;
    background: {
      backgroundColor: string;
    };
    card: {
      backgroundColor: string;
    };
    button: {
      backgroundColor: string;
      borderColor: string;
    };
    card_2: {
      backgroundColor: string;
    };
    textReversed: {
      color: string;
    };
    text: {
      color: string;
    };
  };
} & Theme;

and I want to use it like that in a screen:

const {appColors}: CustomTheme = useTheme();

tickietackie avatar Jan 07 '21 14:01 tickietackie

I use it mostly via the component props, but where I use the useTheme hook I simply cast it to CustomTheme.

const {appColors} = useTheme() as CustomTheme

If you use it in many places, then you could create a wrapper for it.

const useCustomTheme = useTheme as () => CustomTheme
const {appColors} = useCustomTheme()

Please note that I have not tested the wrapper code in an app, simply that it passes type checking. Also I found out how react-native-paper suggests you extend the Theme interface with the global augmentation: https://callstack.github.io/react-native-paper/theming.html#typescript Maybe you should try that first.

KurtMar avatar Jan 07 '21 14:01 KurtMar

I must admit I'm fairly new to ts. So your recommendation to start with the global augmentation didn't work out for me the easy way. My EsLint is complaining about the namespace ("ES2015 module syntax is preferred over custom TypeScript modules and namespaces") and I don't really know how to fix this, neither do I want to mess with the google TSLint recommendations ...

Casting in this case is perhaps the easier way. As long as I am not running into trouble, I will use this as a fast start. Thanks for the fast help 👍

tickietackie avatar Jan 10 '21 20:01 tickietackie

I did this in a global *.d.ts file, and it seems to work alright.

import '@react-navigation/native';

// Override the theme in react native navigation to accept our custom theme props.
declare module '@react-navigation/native' {
  export type ExtendedTheme = {
    dark: boolean;
    colors: {
      primary: string;
      secondary: string;
      tertiary: string;
      danger: string;
      background: string;
      card: string;
      text: string;
      subtext: string;
      separator: string;
      border: string;
      highlight: string;
      notification: string;
    };
  };
  export function useTheme(): ExtendedTheme;
}

And then I declare the theme in a theme.ts file like so:

import { DefaultTheme, DarkTheme, ExtendedTheme } from '@react-navigation/native';

export const light: ExtendedTheme = {
  dark: false,
  colors: {
    ...DefaultTheme.colors,
    primary: 'rgb(216, 11, 140)',
    secondary: 'rgb(0, 174, 239)',
    tertiary: 'rgb(34, 31, 114)',
    danger: 'rgb(208, 2, 27)',
    background: 'rgb(239, 238, 244)',
    card: 'rgb(255, 255, 255)',
    text: 'rgb(0, 0, 0)',
    subtext: 'rgb(102, 102, 102)',
    separator: 'rgb(194, 194, 195)',
    highlight: 'rgb(199, 198, 203)',
  },
};

klandell avatar Jan 21 '21 20:01 klandell

Can you just provide a generic T please?

zmnv avatar Apr 27 '21 14:04 zmnv

@klandell image

does your editor can auto import this? i press and nothing happens..:(

zmnv avatar Apr 27 '21 15:04 zmnv

@zmnv Hmm, I guess auto import in VSCode doesn't work for me either with the type override. I generally just copy and paste that sort of code so I never noticed.

klandell avatar Apr 30 '21 13:04 klandell

@klandell answer works. Thanks!

We can go further by getting the type from the theme itself by usiing typeof. Considering klandell example, we can do something like this in the global.d.ts file:

//import the object theme you created
import { theme } from './theme'

declare module '@react-navigation/native' {
  export type ExtendedTheme = typeof theme
  export function useTheme(): ExtendedTheme;
}

This way you won't need to update your type every time you add/remove a property from your theme object :bulb:

P.S. The vscode auto-import feature doesn't work, indeed, but the autocomplete works.

lcnogueira avatar May 25 '21 09:05 lcnogueira

Great

wandersonalwes avatar Jul 24 '21 20:07 wandersonalwes

Hey guys, can i override the Theme type from react-navigation? Because i use DefaultThe from styled-components, that i override to build my own theme, and i want to override the Theme from react navigation with DefaultTheme from styled-components.

I override the DefaultTheme from react-native-paper with:

declare global {
  namespace ReactNativePaper {
    interface ThemeColors extends DefaultThemeColors {}
    interface Theme extends StyledDefaultTheme {}
  }
}

But i don't know how do the same with react-navigation. Any ideas?

diegodls avatar Jan 29 '22 14:01 diegodls

Thanks for all your help. In my case I'm doing something like this:

types.ts file:

import { Theme as RNNTheme } from "@react-navigation/native";

interface Colors {
  primary: string;
  background: string;
  card: string;
  text: string;
  border: string;
  notification: string;
  bottomTabActiveForeground: string;
  bottomTabInactiveForeground: string;
}

interface Spacing {
  s: number;
  m: number;
  l: number;
  xl: number;
}

export interface Theme extends RNNTheme {
  colors: Colors;
  spacing: Spacing;
}

bottom-tab.tsx file:

import { useTheme } from "@react-navigation/native";
import * as React from "react";

function BottomTab() {
  const { colors, spacing, ... } = useTheme() as Theme;

  return <...>
}

export default BottomTab;

JuanjoFR avatar May 31 '22 18:05 JuanjoFR

Hi everyone, I followed @klandell answer and created a *.d.ts file with the following:

import '@react-navigation/native';

declare module '@react-navigation/native' {
  export type ExtendedTheme = {
    dark: boolean;
    colors: {
      background: string;
      card: string;
      text: string;
      textGrey: string;
    };
  };

  export function useTheme(): ExtendedTheme;
} 

But I still have an error when setting theme props of NavigationContainer: Type 'ExtendedTheme' is not assignable to type 'Theme'

const Navigation = () => {
  const scheme = useColorScheme();
  return (
    <NavigationContainer theme={scheme === 'dark' ? darkTheme : lightTheme}>
      <StackNavigator />
    </NavigationContainer>
  );
};
export default Navigation;

Any idea how I can modify type of theme props ? I am pretty new to TS but I was not able to find the answer anywhere

prometek avatar Jul 03 '22 14:07 prometek

Any idea how I can modify type of theme props ? I am pretty new to TS but I was not able to find the answer anywhere

This is what I recommend:

import {DefaultTheme, Theme} from '@react-navigation/native'

// Define extended theme type that literally *extends* Theme
interface ExtendedTheme extends Theme {
  // Reference the Theme type's colors field and make our field an intersection
  // Learn more:
  //   https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types
  //   https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html
  colors: Theme['colors'] & {
    textGrey: string;
  }
}

export const lightTheme: ExtendedTheme = {
  dark: false,
  colors: {
    // Define your custom colors here
    // If you want to import the default theme's colors, uncomment the following line:
    // ...DefaultTheme.colors
  }
}


export const darkTheme: ExtendedTheme = {
  dark: true,
  colors: {
    // Define your custom colors here
    // If you want to import the default theme's colors, import DarkTheme within the import at the top and uncomment the following line:
    // ...DarkTheme.colors
  }
}

declare module '@react-navigation/native' {
  export function useTheme(): ExtendedTheme
}

evanwalsh avatar Jul 03 '22 15:07 evanwalsh

@evanwalsh thanks it works like a charm !

prometek avatar Jul 03 '22 15:07 prometek

in customTheme.tsx: import {Theme} from '@react-navigation/native'; export interface CustomTheme extends Theme { primary: string; secondary: string; info: string; ...... }


In customTheme.d.tsx: import {CustomTheme} from './customTheme'; //path of the "customTheme.tsx" file declare module '@react-navigation/native' { export function useTheme(): CustomTheme; }


in LightTheme.tsx: import {CustomTheme} from '../interfaces/theme/customTheme'; //path of the "customTheme.tsx" file import {DefaultTheme} from '@react-navigation/native';

const lightTheme: CustomTheme = { ...DefaultTheme, primary: colors.primary, secondary: colors.secondary, info: colors.info, ..... }


import {NavigationContainer} from '@react-navigation/native';
< NavigationContainer theme={lightTheme}> {......} </ NavigationContainer>

It is Correct 100%

Laama-Haddad avatar Dec 19 '22 00:12 Laama-Haddad

This worked for me, adding it to the global.d.ts file

 export type Theme = {
  dark: boolean;
  colors: {
    primary: string;
    background: string;
    card: string;
    inputBg: string;
    text: string;
    border: string;
    notification: string;
  };
};

declare module "@react-navigation/native" { 
  export function useTheme(): Theme;
}

barreraanthony93 avatar Mar 21 '24 18:03 barreraanthony93

In React Navigation 7, you can override the theme with:

import { type Theme as NativeTheme } from '@react-navigation/native';

declare global {
  namespace ReactNavigation {
    interface Theme extends NativeTheme {
      colors: NativeTheme['colors'] & {
        // Your custom colors
        warning: string;
      };
    }
  }
}

satya164 avatar Mar 22 '24 12:03 satya164

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.

github-actions[bot] avatar Mar 22 '24 12:03 github-actions[bot]