简体   繁体   中英

Converting React Higher Order Component (HOC) to TypeScript Causes Error: “Exported variable has or is using private name”

Calling for help from TypeScript experts, I'm running into an error while trying to rewrite a React Higher Order Component (HOC) into TS. I'm not sure how to go about solving this.

"src/withEnv.tsx(15,14): error TS4025: Exported variable 'withEnv' has or is using private name 'WithEnv'."

My code -

import * as React from 'react'
import Context from './context'
import hoistNonReactStatic from 'hoist-non-react-statics'

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

function defaultEnvToProps(context) {
  return {
    env: context,
  }
}

const withEnv = (mapper) => (Component) => {
  class WithEnv extends React.Component {
    static displayName: string
    render() {
      return (
        <Context.Consumer>
          {(context) => {
            const props = Object.assign(
              {},
              typeof mapper === 'function'
                ? mapper(context)
                : defaultEnvToProps(context),
              this.props
            )
            return <Component {...props} />
          }}
        </Context.Consumer>
      )
    }
  }
  WithEnv.displayName = WithEnv(${getDisplayName(Component)})
  hoistNonReactStatic(WithEnv, Component)
  return WithEnv
}

export default withEnv

There's a lot of information that goes into typing this properly. Some of that info, such as the type of the context value, is missing from your question so I have tried to fill in the blanks.

I can explain the primary error that you are concerned about and tell you how to fix it.

"src/withEnv.tsx(15,14): error TS4025: Exported variable 'withEnv' has or is using private name 'WithEnv'."

There is a discussion on GitHub which will tell you more about this issue. Typescript chokes because it wants to describe the return type of your withEnv HOC as the WithEnv private class. But the declarations file won't include this class because it only exists inside your function, so it cannot be used as a return type. In order to avoid the error you can manually type the return type of withEnv as some component which takes a particular set of props.

import * as React from 'react'
import { ComponentType } from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics'

type ContextValue = {} // what is this ??????

declare const Context: React.Context<ContextValue>;

function getDisplayName(WrappedComponent: ComponentType<any>): string {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

function defaultEnvToProps(context: ContextValue): { env: ContextValue } {
  return {
    env: context,
  }
}

// define a type for a function that maps your context to props
type Mapper<Return> = (context: ContextValue) => Return;

// generic type `MappedProps` describes the props returned by the mapper
// default value for `MappedProps` type matches `defaultEnvToProps`
export const withEnv = <MappedProps extends {} = { env: ContextValue }>(
  // mapper is an optional function that maps from the context value to the added props
  mapper?: Mapper<MappedProps>
) =>
  // returned function is generic depending on the component props
  <Props extends {}>(
    // takes a component which uses the added props
    Component: ComponentType<MappedProps & Omit<Props, keyof MappedProps>>
    // returns a component which doesn't need those props
  ): ComponentType<Omit<Props, keyof MappedProps>> => {
    // you could just as well use a function component here and use `useContext`
    class WithEnv extends React.Component<Omit<Props, keyof MappedProps>> {
      static displayName: string;
      render() {
        return (
          <Context.Consumer>
            {(context) => {
              const mappedProps = mapper ? mapper(context) : defaultEnvToProps(context);
              return (
                <Component
                  {...this.props}
                  {...mappedProps as MappedProps} // this assertion is only necessary is `mapper` is optional
                />
              )
            }}
          </Context.Consumer>
        )
      }
    }
    WithEnv.displayName = `WithEnv(${getDisplayName(Component)})`;
    hoistNonReactStatic(WithEnv, Component);
    return WithEnv;
  }

Typescript Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM