'TypeScript: dealing with `this` component properties which are reffering neither to state, nor to props

Here are my interfaces:

export type alphaColorProcessing = (opacity: number) => string | string[];

export interface welcomeMapping {
  [key: string]: alphaColorProcessing | string;
}
export interface IPalette {
  [key: string]: string | string[] | alphaColorProcessing | welcomeMapping;
}
export interface IThemeContext {
  theme: string;
  palette: IPalette;
  setTheme?: (theme: string) => void;
}

export interface IThemeState extends IThemeContext {
  isDark: Boolean;
}

export interface IAppState {
  loading: boolean;
  themeState: IThemeState;
}

export interface IAppProps {
  setTheme?: (theme: string) => void;
}

I am using these in App component:

enter image description here

But I have the problem: while declaring a method on this, in constructor, as I understand it has nothing to do with IAppState. So my question is, how can I declare/use an interface for the methods which are refferring neither to State, nor to Props? I need for it for the methods/properties that I'm setting on this of the component.

netInfoSubscription, setTheme - is what I am interested in.

here's the code:

export default class App extends React.PureComponent<{},IAppState>  {
  showedForce = false;

  showedBadIP = false;

  constructor(props) {
    super(props);
    this.setTheme = (theme:string) => {
      this.setState((state) => ({
        themeState: {
          ...state.themeState,
          theme,
          isDark: theme === 'dark',
          palette: Palette,
        },
      }));
    };
    this.state = {
      loading: true,
      themeState: {
        theme: 'light',
        isDark: false,
        palette: Palette,
        setTheme: this.setTheme,
      },
    };
  }

  componentDidMount() {
    NetInfo.configure({
      reachabilityUrl: 'https://www.cisco.com/',
    });
    this.netInfoSubscription = NetInfo.addEventListener((state) => {
      handleConnectionStatus(state.isConnected);
    });
  }

  render() {
    const { themeState, loading } = this.state;
    if (loading) return null;
    return (
      <Provider store={ Store }>
        <AppearanceProvider>
          <SafeAreaProvider>
            <Root>
              <ThemeContext.Provider value={ themeState }>
                <Navigation />
                <FingerprintModal />
              </ThemeContext.Provider>
            </Root>
          </SafeAreaProvider>
        </AppearanceProvider>
      </Provider>
    );
  }
}


Solution 1:[1]

There are two related things you've asked about: setTheme and netInfoSubscription.

You have several options:

Method syntax

Your setTheme is a method, so you could write it as one, and use bind in the constructor:

export default class App extends React.PureComponent<{},IAppState>  {
    constructor(props) {
        // ...
        this.setTheme = this.setTheme.bind(this);
        // ...
    }

    setTheme(theme: string) {
        // ...
    }
}

One advantage of that is that setTheme is on App.prototype, which is sometimes helpful for unit testing (it can be mocked).

That works for setTheme, but not for netInfoSubscription; you'll need one of the other options below for that.

Property declarations (including the method)

You can write your setTheme as a property (and of course, netInfoSubscription is also a property):

export default class App extends React.PureComponent<{},IAppState>  {
    setTheme = (theme: string) => {
        // ...
    };
    netInfoSubscription?: SubscriptionType;
    // Or:
    netInfoSubscription: SubscriptionType | null = null;
}

For setTheme, since that's an arrow function, it doesn't need binding. The context it closes over has this referring to the instance being constructed.

For netInfoSubscription, since we don't have a value for it during instance construction, there are two options:

  • Use ? to indicate that the property is optional.
  • Use a union type with a value like null that we assign until/unless we have the subscription.

Note that either of those means that netInfoSubscription may be undefined (or null) anywhere in the code. For places where you know it's not (but TypeScript doesn't), you can grab it to a local constant and do an assertion:

someMethod() {
    // If we *know* (from the logic) that we have the subscription now
    const { netInfoSubscription } = this;
    assertNotNullish(netInfoSubscription);
    // ...here you can use `netInfoSubscription`'s value, and TypeScript will understand
    // that we now know it has one
    // ...
}

assertNotNullish is one of my standard utility functions, which looks like this:

const assertNotNullish: <T>(value: T | undefined | null) => asserts value is T = (value) => {
    if (value == null) { // Checks `null` and `undefined`
        throw new Error(`Expected value to be non-nullish`);
    }
};

Playground link

Note: You could just use the non-null assertion operator, but I wouldn't. Using something like assertNotNull does a runtime check that what you're asserting is true. Using the non-null assertion operator doesn't, it just tells TypeScript to assume the assertion is true.

An interface

You could define an interface:

interface AppInstanceMembers {
    setTheme: (theme: string) => void;
    netInfoSubscription?: SubscriptionType;
    // ...
}
// ...
export default class App extends React.PureComponent<{},IAppState> & AppInstanceMembers {
// ????????????????????????????????????????????????????????????????^^^^^^^^^^^^^^^^^^^^
    constructor(props) {
        // ...
        this.setTheme = (theme/* You don't have to repeat the type here*/) => {
            // ...
        };
        // ...
    }
    // ...
}

That tells TypeScript to expect a setTheme (and possibly netInfoSubscription) on App (which you satisfy in the constructor). But it does mean you have to repeat some parts of setTheme.

As shown earlier, in places where you know from the logic (but TypeScript doesn't) that netInfoSubscription won't be nullish, you can use assertNotNullish.

Solution 2:[2]

Easy

write function body outside of the constructor

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Kunal Gupta