Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: Inject props through HOC without declaring types for them

I'm trying to do something kinda connect() in react-redux bindings. Here's my HOC that injects props in a component:

export function withAdditionalProps<T, I>(
  injectedProps: I,
  WrappedComponent: React.ComponentType<T>,
): React.ComponentType<Omit<T, keyof I>> { ... }

It works okay if I declare injected props types (I generic type), but what If I want to do a HOC without declaring these types (omit injected props keys on-fly). How can I determine keys from the passed props? I tried, for example, something like this:

export function withAdditionalProps<T>(
  injectedProps: { [key: string]: unknown },
  WrappedComponent: React.ComponentType<T>,
): React.ComponentType<Omit<T, keyof typeof injectedProps>> { ... }

const InjectedComponent = withAdditionalProps<AppState>(
  {
     counter: 0
  },
  (props) => (<div>{props.counter}</div>)
);

But it's not working correct: compiler throws an error when rendering the component. Look at the screenshot (testProp is a "native" prop of a component) Problem Maybe anyone can help me.

like image 900
phen0menon Avatar asked May 13 '20 18:05

phen0menon


1 Answers

In short, your second example isn't possible yet in Typescript.

The issue is that the { [key:string]: unknown } type always captures all possible strings as keys, rather than narrowing to the concrete ones you use in a particular call, which would make usage of Omit here possible.

As it is, Omit used with { [key:string]: unknown } simply omits all possible keys and therefore all your native props in T. This may be possible in future via the negated types feature, but for now the only way is via declared type variables as in your first example.

However, those declared type variables in the function definition don't oblige you to also declare them for each call. See the code below - the compiler will infer both T and I from the concrete arguments you pass when you call the function. So in practice the only difference here is in the type definitions for your HOC, and honestly it seems like similar effort/readability either way.

function foo<T, I>(t: T, i: I): Omit<T, keyof I> { 
    return { ...t, ...i }
}

// notice explicitly declaring foo<{ a: number, b: number}, { c: number }> 
// is NOT necessary. The correct types are just inferred from the args you pass.
const bar = foo({ a: 1, b: 2 }, { b: 3 })

bar.a // OK
bar.b // ERROR, since b is omitted
like image 147
davnicwil Avatar answered Oct 02 '22 00:10

davnicwil