isomorphic-style-loader icon indicating copy to clipboard operation
isomorphic-style-loader copied to clipboard

TypeScript Definition Help

Open danielmhanover opened this issue 7 years ago • 13 comments

Hi all,

I'm working on a TypeScript definition for withStyle. What I have so far is:

type identity<T> = (arg: T) => T;

//TODO: This could be better, specific type information about the component is lost
export default function withStyles<P, S, T>(style: any): identity<new (...args: T[]) => React.Component<P, S>>

While this is OK, clearly some type information about the component is being lost, and I'm not too sure what type to use for the style object. Any ideas how to make this better?

Note that

export default function withStyles<T extends React.Component>(style: any): identity<T>

only works if you specify the return type (with withStyles<ComponentName>). Trying to avoid explicit generics if possible.

danielmhanover avatar Aug 20 '17 05:08 danielmhanover

@danielmhanover did you ever get this fully functional?

jakemmarsh avatar May 04 '18 21:05 jakemmarsh

I just use

import withStyles from "isomorphic-style-loader/lib/withStyles"

export default function injectTSFriendlyStyles<T extends React.ComponentClass>(s: any, t: T): T {
    return withStyles(s)(t) as T
}

danielmhanover avatar May 17 '18 22:05 danielmhanover

@danielmhanover Did you create this in a .d.ts file? Your code gives me the following error:

  TS7016: Could not find a declaration file for module 'isomorphic-style-loader/lib/withStyles'. '\node_modules\isomorphic-style-loader\lib\withStyles.js' implicitly has an 'any' type

and [ts] An implementation cannot be declared in ambient contexts.

I'm assuming you save it in a .tsx file and then use it like injectTSFriendlyStyles(s,Widget)

This would preclude me from composing it with other hocs, such as injectIntl however..

damiangreen avatar Sep 20 '18 11:09 damiangreen

This seems to work fine for me in global.d.ts

declare module 'isomorphic-style-loader/lib/withStyles' {
  export interface Styles {
    [key: string]: string;
  }

  const withStyles = (style: Styles) => <T extends React.ComponentClass<P, S>>(
    component: T,
  ): T => T;

  export default withStyles;
}

declare module '*.scss' {
  import { Styles } from 'isomorphic-style-loader/lib/withStyles';

  const value: Styles;

  export = value;
}

declare module '*.css' {
  import { Styles } from 'isomorphic-style-loader/lib/withStyles';

  const value: Styles;

  export = value;
}

after this I am able to use withStyles in my component as below

import withStyles from 'isomorphic-style-loader/lib/withStyles';
import * as React from 'react';
import * as style from './ComponentStyle.scss';

@withStyles(style)
export class MyComponent extends React.Component {
 // component definition
}

sumit-sinha avatar Jan 03 '19 14:01 sumit-sinha

@sumit-sinha Tihs is getting closer. However it falls down when passing multiple styles, e.g. withStyles( normalizeCss, antd,

also receive the warning a const initializer in an ambient context must be a string or numeric literal

This caters for multiple styles:

declare module 'isomorphic-style-loader/lib/withStyles' {
  export interface Styles {
    [key: string]: string
  }

   const withStyles = (...styles: Styles[]) => <
    T extends React.ComponentClass<P, S>
  >(
    component: T,
  ): T => T

  export default withStyles
}

declare module '*.scss' {
  import { Styles } from 'isomorphic-style-loader/lib/withStyles'

  const value: Styles

  export = value
}

declare module '*.css' {
  import { Styles } from 'isomorphic-style-loader/lib/withStyles'

  const value: Styles

  export = value
}

damiangreen avatar Feb 20 '19 09:02 damiangreen

@damiangreen : when I try that, I get:

    ERROR in [at-loader] ./global.d.ts:6:22
        TS1254: A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.

    ERROR in [at-loader] ./global.d.ts:7:36
        TS2304: Cannot find name 'P'.

    ERROR in [at-loader] ./global.d.ts:7:39
        TS2304: Cannot find name 'S'.

    ERROR in [at-loader] ./global.d.ts:10:11
        TS2693: 'T' only refers to a type, but is being used as a value here.

flyrev avatar May 25 '19 12:05 flyrev

Edit: Oops that didn't work. I've updated the snippet

I was getting the same errors as @flyrev . Maybe some differences in TS versions.

This should fix that and should also support functional components.

interface Styles {
  [key: string]: string;
}

declare module 'isomorphic-style-loader/withStyles' {
  function withStyles(
    ...styles: Styles[]
  ): <P, S, T extends React.ComponentClass<P, S> | React.FunctionComponent<P>>(
    component: T
  ) => T;

  export = withStyles;
}

declare module '*.scss' {
  const value: Styles;

  export = value;
}

declare module '*.css' {
  const value: Styles;

  export = value;
}

NoxHarmonium avatar Feb 18 '20 00:02 NoxHarmonium

@NoxHarmonium 's answer works well.

For completeness we had to add

declare module 'isomorphic-style-loader/useStyles' {
  const useStyles = (...styles: Styles[]) => any

  export default useStyles
}

damiangreen avatar Feb 19 '20 16:02 damiangreen

@damiangreen I'm getting a pretty cryptic ts error: 1254: A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.

Any chance you know what that might be?

antgonzales avatar Mar 03 '20 18:03 antgonzales

nvm figured it out:

declare module 'isomorphic-style-loader/useStyles' {
  function useStyles(...styles: Styles[]): void;
  export = useStyles
}

antgonzales avatar Mar 03 '20 18:03 antgonzales

Following the thread, this is a sugestion for *.[s]css files, useStyles hook and StyledContext in global.d.ts file:

type Dispose = () => void
type InsertCssItem = () => Dispose
type GetCSSItem = () => string
type GetContent = () => string

interface Style {
  [key: string]: InsertCssItem | GetCSSItem | GetContent | string
  _insertCss: InsertCssItem
  _getCss: GetCSSItem
  _getContent: GetContent
}

declare module '*.scss' {
  const style: Style
  export default style
}

declare module '*.css' {
  const style: Style
  export default style
}

declare module 'isomorphic-style-loader/useStyles' {
  function useStyles(...styles: Style[]): void
  export default useStyles
}

declare module 'isomorphic-style-loader/StyleContext' {
  import { Context } from 'react'

  type RemoveGlobalCss = () => void
  type InsertCSS = (...styles: Style[]) => RemoveGlobalCss | void
  interface StyleContextValue {
    insertCss: InsertCSS
  }

  const StyleContext: Context<StyleContextValue>

  export { StyleContext as default, InsertCSS }
}

ddsilva avatar Aug 17 '20 03:08 ddsilva

Hello @danielmhanover, are you still working on the typescript? I am expecting @types/isomorphic-style-loader. I tried with above suggestions but still seeing issues. Would you or anyone has a working solution?

Thanks in advance.

jknanda78 avatar Feb 02 '21 17:02 jknanda78

Building on @NoxHarmonium I got this working with less. I made some minor changes to get everything to work with Functional components. Thank you for the solution. I hope this helps someone ...

// global.d.ts
interface Styles {
  [key: string]: string;
}

declare module 'isomorphic-style-loader/withStyles' {
  function withStyles(
    ...styles: Styles[]
  ): <P, S>(
    component: React.FunctionComponent<P> | React.ComponentClass<P, S>,
  ) => React.FunctionComponent<P> | React.ComponentClass<P, S>;

  export = withStyles;
}

declare module '*.less' {
  const value: Styles;

  export = value;
}

declare module '*.css' {
  const value: Styles;

  export = value;
}
// SomeComponent.less

.root {
   color: red;
}
// SomeComponent.tsx 
import React, { FunctionComponent } from 'react';
import withStyles from 'isomorphic-style-loader/withStyles';
 
const SomeComponent: FunctionComponent<{
  foo: string;
}> = ({ foo = 'this is red }) => <div className={s.root}>{foo}</div>;

export default withStyles(s)(SomeComponent);

arnigudj avatar Sep 14 '21 12:09 arnigudj