React-native-web: Using TypeScript x React Native Web

Created on 21 Feb 2018  Â·  26Comments  Â·  Source: necolas/react-native-web

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;
  `
    )
  );
}

Most helpful comment

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

All 26 comments

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?

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.

@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.

Happy to include something in the docs as a start

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

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

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

Yeah it’s incomplete for sure.

@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.

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

"@types/react-native": "mygithubusername/myrepo",

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?

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

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).

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

@ethanneff Thank you! Works like a charm :)

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

@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;

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>

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>

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

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.

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

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

@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.

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.

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.

Was this page helpful?
0 / 5 - 0 ratings