necolas / react-native-web

Cross-platform React UI packages
https://necolas.github.io/react-native-web
MIT License
21.62k stars 1.79k forks source link

Using TypeScript x React Native Web #832

Closed jaredpalmer closed 6 years ago

jaredpalmer commented 6 years ago

For those that are interested, this is how I am monkey patching @types/react-native to work with React Native Web. This postinstall script fixes the react-native / node conflict and adds my RN-Web -specific types to @types/react-native without augmenting any of the core @types/react-native typings. This is because TS will effectively combine interface declarations with the same name. My typings are as of 0.4.0, but should work with 0.5.0. The only thing that is a little hand-wavy is that TextStyle has a resize?: string that only actually applies to TextInput. This is documented in the comments and should thus show up in VSCode autocomplete. Hope this helps anyone else.

@necolas lmk if you'd be willing to accept a PR for either docs and/or for the typings. Could be nice to add a react-native-web/ts-setup.js that runs the script below.

// ./package.json
{
  "scripts": {
    "postinstall": "node ./postinstall.js"
  }
}
// ./postinstall.js
'use strict';

const fs = require('fs');

const RN_TSD = __dirname + '/node_modules/@types/react-native/index.d.ts';
const raw = fs.readFileSync(RN_TSD);

// Fix @types/node conflict
// @see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/15960
// @see https://gist.github.com/rawrmaan/be47e71bd0df3f7493ead6cefd6b400c
fs.writeFileSync(
  RN_TSD,
  raw.toString().replace('declare global', 'declare namespace RemovedGlobals')
);

// Add React Native Web Types to @types/react-native.
if (!raw.includes('REACT-NATIVE-WEB TYPINGS')) {
  fs.writeFileSync(
    RN_TSD,
    raw.toString().replace(
      `dismiss: () => void;\n}`,
      `dismiss: () => void;\n}
      //////////////////////////////////////////////////////////////////////////
      //
      //  REACT-NATIVE-WEB TYPINGS
      //
      //////////////////////////////////////////////////////////////////////////
      export interface ImageProperties {
          /** 
           * When false, the image will not be draggable 
           * @platform web
           */
          draggable?: boolean;
      }

      export interface TextInputProperties {
          /** 
           * Indicates whether the value of the control can be automatically completed by the browser
           * @platform web
           */
          autoComplete?: string;
      }

      export interface SwitchProperties {
          /** 
           * The color of the thumb grip when the switch is turned on. 
           * @platform web
           */
          activeThumbColor?: string;
          /** 
           * The color of the track when the switch is turned on.  
           * @platform web
           */
          activeTrackColor?: string;
          /** 
           * The color of the thumb grip when the switch is turned off. 
           * @platform web
           */
          thumbColor?: string;
          /** 
           * The color of the track when the switch is turned off.
           * @platform web
           */
          trackColor?: string;
      }

      export interface TextStyle {
          /** @platform web */
          fontFeatureSettings?: string;
          /** @platform web */
          textIndent?: string;
          /** @platform web */
          textOverflow?: string;
          /** @platform web */
          textRendering?: string;
          /** @platform web */
          textTransform?: string;
          /** @platform web */
          unicodeBidi?: string;
          /** @platform web */
          wordWrap?: string;
          /** 
           * TextInput only! 
           * @platform web 
           */
          resize?: string;
      }

      export interface ViewStyle {
          /** @platform web */
          animationDelay?: string;
          /** @platform web */
          animationDirection?: string;
          /** @platform web */
          animationDuration?: string;
          /** @platform web */
          animationFillMode?: string;
          /** @platform web */
          animationName?: string | Array<Object>;
          /** @platform web */
          animationIterationCount?: number | "infinite";
          /** @platform web */
          animationPlayState?: string;
          /** @platform web */
          animationTimingFunction?: string;
          /** @platform web */
          backgroundAttachment?: string;
          /** @platform web */
          backgroundBlendMode?: string;
          /** @platform web */
          backgroundClip?: string;
          /** @platform web */
          backgroundImage?: string;
          /** @platform web */
          backgroundOrigin?: string;
          /** @platform web */
          backgroundPosition?: string;
          /** @platform web */
          backgroundRepeat?: string;
          /** @platform web */
          backgroundSize?: string;
          /** @platform web */
          boxShadow?: string;
          /** @platform web */
          boxSizing?: string;
          /** @platform web */
          clip?: string;
          /** @platform web */
          cursor?: string;
          /** @platform web */
          filter?: string;
          /** @platform web */
          gridAutoColumns?: string;
          /** @platform web */
          gridAutoFlow?: string;
          /** @platform web */
          gridAutoRows?: string;
          /** @platform web */
          gridColumnEnd?: string;
          /** @platform web */
          gridColumnGap?: string;
          /** @platform web */
          gridColumnStart?: string;
          /** @platform web */
          gridRowEnd?: string;
          /** @platform web */
          gridRowGap?: string;
          /** @platform web */
          gridRowStart?: string;
          /** @platform web */
          gridTemplateColumns?: string;
          /** @platform web */
          gridTemplateRows?: string;
          /** @platform web */
          gridTemplateAreas?: string;
          /** @platform web */
          outline?: string;
          /** @platform web */
          outlineColor?: string;
          /** @platform web */
          overflowX?: string;
          /** @platform web */
          overflowY?: string;
          /** @platform web */
          overscrollBehavior?: "auto" | "contain" | "none";
          /** @platform web */
          overscrollBehaviorX?: "auto" | "contain" | "none";
          /** @platform web */
          overscrollBehaviorY?: "auto" | "contain" | "none";
          /** @platform web */
          perspective?: string;
          /** @platform web */
          perspectiveOrigin?: string;
          /** @platform web */
          touchAction?: string;
          /** @platform web */
          transformOrigin?: string;
          /** @platform web */
          transitionDelay?: string;
          /** @platform web */
          transitionDuration?: string;
          /** @platform web */
          transitionProperty?: string;
          /** @platform web */
          transitionTimingFunction?: string;
          /** @platform web */
          userSelect?: string
          /** @platform web */
          visibility?: string;
          /** @platform web */
          willChange?: string;
      }

      export interface TextProperties {
          /** 
           * Allows assistive technologies to present and support interaction with the view in a manner that is consistent with user expectations for similar views of that type. For example, marking a touchable view with an accessibilityRole of button. For compatibility with React Native accessibilityTraits and accessibilityComponentType are mapped to accessibilityRole. (This is implemented using ARIA roles.) 
           * @platform web
           */
          accessibilityRole?: 'button' | 'heading' | 'label' | 'link' | 'listitem';
      }

      export interface CheckBoxProps extends ViewProperties {
          /** 
           * Invoked with the event when the value changes. 
           * @platform web
           */
          onChange?: Function;
          /** 
           * Invoked with the new value when the value changes. 
           * @platform web
           */
          onValueChange?: Function;
          /** 
           * The value of the checkbox. If \`true\` the checkbox will be checked. 
           * @platform web
           */
          value?: boolean;
          /** 
           * If true, the user won't be able to interact with the checkbox. 
           * @platform web
           */
          disabled?: boolean;
          /** 
           * Customize the color of the checkbox.
           * @platform web  
           */
          color?: string;
      }

      export interface CheckBoxStatic extends React.ComponentClass<CheckBoxProps> {}
      export type CheckBox = CheckBoxStatic;
  `
    )
  );
}
necolas commented 6 years ago

I'm not familiar with TypeScript but probably don't want to maintain types for it (as RN doesn't either). Is this something that can be added to @types/react-native?

kristerkari commented 6 years ago

Adding that script to automatically run on react-native-web post install is not that good idea. It could easily break things if things change in @types/react-native.

@jaredpalmer Why don't you just create a repository that has the script and instructions on how to use it? There could be a mention about the script in react-native-web docs. That way anyone who needs it can add it to their project.

jaredpalmer commented 6 years ago

@kristerkari I think that is the best way forward. Imho the script is about as fragile as the typings themselves, but I get your point.

necolas commented 6 years ago

Happy to include something in the docs as a start

nickdima commented 6 years ago

@jaredpalmer this looks great! Any chance you will publish this?

jaredpalmer commented 6 years ago

I’ve updated this a little since posting. There are significant differences related to strings vs. numbers in the types. Will try to publish soon

nickdima commented 6 years ago

@jaredpalmer that's great to hear. One type annotation that I noticed was missing is the one for the function AppRegistry.getApplication.

jaredpalmer commented 6 years ago

Yeah it’s incomplete for sure.

teebot commented 6 years ago

@jaredpalmer I was wondering. If we had a @types/react-native-web package maybe we could find a way to alias @types/react-native to that? I mean the same way webpack aliases react-native as react-native-web.

kristerkari commented 6 years ago

@teebot yeah it is possible to do this in package.json:

"@types/react-native": "mygithubusername/myrepo",
nickdima commented 6 years ago

I'm a newbie at TypeScript so this may sound stupid, but: would it be possibile to have @types/react-native-web "globally" extend @types/react-native and only add the stuff that is missing?

necolas commented 6 years ago

We can add something to the docs once y'all figure this out. Thanks :)

rakannimer commented 6 years ago

I'm currently creating a @types/react-native-web alias to @types/react-native using a yarn/npm hack :

yarn add -D @types/react-native-web@npm:@types/react-native

npm :

npm i -D @types/react-native-web@npm:@types/react-native

Not the best way, but it works(-ish).

ethanneff commented 6 years ago

If anyone else finds this thread, I made a boilerplate for react-native-web with typescript: https://github.com/ethanneff/react-native-web-typescript

AleksandarAleksandrov commented 6 years ago

@ethanneff Thank you! Works like a charm :)

lukepighetti commented 6 years ago

Thank you @ethanneff will give it a try. In the mean time, does ‘react-native-web’ team recommend any particular type system?

steida commented 5 years ago

@types/react-native-web does not exists. @ethanneff does not solve the problem with React Native Web custom types.

Because there are no TypeScript types for React Native Web, my workaround is wrappers with types added as we go.

import React from 'react';
import { Text, TextProps } from 'react-native';

// Add React Native Web custom props.
type AppTextProps = TextProps & {
  accessibilityRole: 'link';
  onMouseEnter: () => void;
  onMouseLeave: () => void;
};

const AppText: React.FunctionComponent<AppTextProps> = props => {
  return <Text {...props} />;
};

export default AppText;
steida commented 5 years ago

Hmm, but that's not correct for universal components. This is the better and spread operator is not type checked. It's an interesting thing to think by the way. How to properly type different platforms? I believe it should be possible with TypeScript conditional types. Aka, accessibilityRole: 'link' only in web platform etc. Meanwhile, I use this:

<Text
  {...Platform.select({
    web: {
      accessibilityRole: 'link',
      onMouseEnter: () => setIsActive(true),
      onMouseLeave: () => setIsActive(false),
    },
  })}
  style={[
    style || theme.link,
    (isActive || routeIsActive()) && (activeStyle || theme.linkActive),
  ]}
>
  {children}
</Text>
slorber commented 5 years ago

Also looking for a decent option for using RNW with TS

@steida Instead of wrapping in new FC, why not just playing with types and adding the link?

type TextProps = React.ComponentProps<typeof Text>
type WebTextProps = TextProps & {
  href?: string
}
const FixedText = Text as ComponentType<WebTextProps>
regalstreak commented 5 years ago

Any updates on typescript support for RNW or is aliasing still the way for types?

mikeaustin commented 4 years ago

What's the current supported solution to use Create React App with TypeScript and React Native for Web? I just went to start a new project and realized it doesn't work out of the box.

BrianLi101 commented 3 years ago

My team has been using react-native-web and ended up creating a repo with the type conversions from Flow to TypeScript. We haven't completed translations for all of the types just yet but have covered most of the highly used UI components. Hopefully, this helps!

https://github.com/gtechnologies/react-native-web-ts-types

nandorojo commented 3 years ago

I successfully added TS support to React Native Web with a single file. Declaration merging handles this for you.

You can see my solution here: https://github.com/necolas/react-native-web/issues/1684#issuecomment-766451866

intergalacticspacehighway commented 3 years ago

@nandorojo Can you share the file If possible? I'd love to contribute with recent 0.15 accessibility changes in RN Web. Also, I need it for one of my projects.

nandorojo commented 3 years ago

I don't have a fully-made file yet unfortunately, I've just been going prop-by-prop as shown in my linked comment. It would be awesome to have this, though.

intergalacticspacehighway commented 3 years ago

Cool, no worries. Looks like a bit of work for one person 😅. Should we have a temp repo for this, till we figure out something? A single file will make it painful to update though.

sstur commented 2 years ago

I successfully added TS support to React Native Web with a single file.

@nandorojo, would you be keen to share your d.ts file that uses declaration merging. There's still nothing on @types/react-native-web and I don't want to have to augment all the interfaces by hand if folks in the community have done this already. Thanks in advance!

Edit: this seems to have some level of type coverage, but it's not using the approach you mentioned and I'm a bit skeptical that it's up to date with RNW.

nandorojo commented 2 years ago

here you go:


// react-native-web/overrides.ts
declare module 'react-native' {
  interface PressableStateCallbackType {
    hovered?: boolean
    focused?: boolean
  }
  interface ViewStyle {
    transitionProperty?: string
    transitionDuration?: string
  }
  interface TextProps {
    accessibilityComponentType?: never
    accessibilityTraits?: never
    href?: string
    hrefAttrs?: {
      rel: 'noreferrer'
      target?: '_blank'
    }
  }
  interface ViewProps {
    accessibilityRole?: string
    href?: string
    hrefAttrs?: {
      rel: 'noreferrer'
      target?: '_blank'
    }
    onClick?: (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void
  }
}
sstur commented 2 years ago

Thanks @nandorojo for getting me started here! I added a bunch more types that are relevant to my codebase, in case anyone else finds these helpful, I've dropped it into a Gist here: https://gist.github.com/sstur/9693aa41e95078c8c6bcf3d8f1216872

mikehardy commented 2 years ago

@nandorojo looks like 'ViewStyle' needs a cursor prop ;-) https://github.com/necolas/react-native-web/issues/1847#issuecomment-1066233154 Nice solution on the overrides types file, would be excellent to see it mainlined here

@sstur I do not see an import 'react-native' in your overrides, how do you combine the base types and your overrides?

sstur commented 2 years ago

I don't remember, I think it just works without that.

Frexuz commented 2 years ago

AppRegistry.getApplication still missing

bryanltobing commented 1 year ago

here you go:

// react-native-web/overrides.ts
declare module 'react-native' {
  interface PressableStateCallbackType {
    hovered?: boolean
    focused?: boolean
  }
  interface ViewStyle {
    transitionProperty?: string
    transitionDuration?: string
  }
  interface TextProps {
    accessibilityComponentType?: never
    accessibilityTraits?: never
    href?: string
    hrefAttrs?: {
      rel: 'noreferrer'
      target?: '_blank'
    }
  }
  interface ViewProps {
    accessibilityRole?: string
    href?: string
    hrefAttrs?: {
      rel: 'noreferrer'
      target?: '_blank'
    }
    onClick?: (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => void
  }
}

do we still need to do this?

blazejkustra commented 2 months ago

Happy to include something in the docs as a start

We can add something to the docs once y'all figure this out. Thanks :)

Types for react-native-web are now available on DefinitelyTyped.

@necolas Are you still open to the idea of making a mention in the docs?

necolas commented 2 months ago

Yeah sounds good. Thanks!