Reputation: 2027
I am using react native with typescript. I have a component with the following structure
component
- component.ios.tsx
- component.android.tsx
Now I want to import the component. So, I am doing this:
import component from '../component/component';
But it says:
[ts] Cannot find module ../component/component
The location is correct. I need to add something to the tslint file to make it understand.
I also tried to do:
let component = require('../component/component');
This didn't give typescript error. But it gave a runtime error.
element type is invalid expected a string or a class/function but got undefined
Has anybody run into this issue? Thank you.
Upvotes: 32
Views: 11794
Reputation: 601
Add the following line into tsconfig.json
under compilerOptions
:
"moduleSuffixes": [".ios", ".android", ".native", ""]
e.g.:
{
...
"compilerOptions": {
...
"moduleSuffixes": [".ios", ".android", ".native", ""]
},
...
}
Upvotes: 24
Reputation: 21
Folder:
component/
- Test
- index.android.tsx
- index.ios.tsx
- index.d.ts
- types.d.ts
- index.ts
Test/index.android.tsx:
import React from 'react';
import { Text } from 'react-native';
import { TestProps } from './types';
function Test(props: TestProps) {
return <Text>Test android</Text>;
}
export default Test;
Test/index.ios.tsx:
import React from 'react';
import { Text } from 'react-native';
import { TestProps } from './types';
function Test(props: TestProps) {
return <Text>Test ios</Text>;
}
export default Test;
Test/index.d.ts:
import { TestProps } from './types';
export default function Test(props: TestProps): React.JSX.Element;
component/index.ts:
export { default as Test } from './components/Test';
use Test:
import React from 'react';
import { Test } from '@components';
function PageA() {
return <Test />
}
export default PageA;
I've got this TypeScript code running without complaints and with the right types.
Upvotes: 2
Reputation: 1761
It's not the best solution since consumers of the component can still import the wrong things, but it's a step in the right direction. We metro bundler can find the proper files and we get type safety
Folder
Testing.tsx
import React from 'react';
import { StyleSheet } from 'react-native';
import PlatformImage from '/ui/corex/PlatformImage';
const Testing = () => {
return (
<PlatformImage
style={styles.prefixImage}
borderRadius={styles.prefixImage.borderRadius}
source={{
uri: 'my uri',
priority: 'high',
}}
/>
);
};
const styles = StyleSheet.create({
prefixImage: {
resizeMode: 'contain',
width: '100%',
height: '100%',
borderRadius: 40,
},
});
export default Testing;
PlatformImage.d.ts
import { ImageStyle, StyleProp } from 'react-native';
import { Priority } from 'react-native-fast-image';
/**
* An restricted list of props as we don't currently want to overcomplicate the code by supporting more
* props than we actually need. If we need specific props that are supported by react-native.Image or
* react-native-fast-image, we can add them as we need them.
*/
export interface PlatformImageProps {
testID?: string;
style?: StyleProp<ImageStyle>;
source: {
uri: string;
/**
* Only used in ios
*/
priority: Priority;
};
/**
*
* Only used on android for border radius (since styles.borderRadius doesn't work for android). For ios, specify in styles.
* Ideally the api for this component should be the same for both android & ios,
* but this would mean preventing a styles.borderRadius from being used in ios in favor of using the
* borderRadius prop, but this could be messy to code & maintain.
*/
borderRadius?: number;
resizeMode?: 'contain' | 'cover' | 'stretch' | 'center';
/**
* Invoked when load either succeeds or fails
*/
onLoadEnd?: () => void;
}
declare const PlatformImage: React.ComponentType<PlatformImageProps>;
export default PlatformImage;
index.tsx
import PlatformImage from '/ui/corex/PlatformImage/PlatformImage';
export default PlatformImage;
PlatformImage.android.tsx
import React from 'react';
import { Image } from 'react-native';
import { PlatformImageProps } from '/ui/corex/PlatformImage/PlatformImage';
/**
* Components should never import this directly, but should rather import the index.tsx file
*/
const PlatformImage = ({
testID,
style,
source,
resizeMode = 'cover',
borderRadius,
onLoadEnd,
}: PlatformImageProps) => {
console.log('running android');
return (
<Image
testID={testID}
// todo simon: fix this typescript error
style={style}
resizeMode={resizeMode}
borderRadius={borderRadius}
onLoadEnd={onLoadEnd}
source={source}
/>
);
};
export default PlatformImage;
PlatformImage.ios.tsx
import React from 'react';
import FastImage from 'react-native-fast-image';
import { PlatformImageProps } from '/ui/corex/PlatformImage/PlatformImage';
/**
* Components should never import this directly, but should rather import the index.tsx file
*/
const PlatformImage = ({
testID,
style,
source,
resizeMode = 'cover',
onLoadEnd,
}: PlatformImageProps) => {
console.log('running ios');
return (
<FastImage
testID={testID}
// todo simon: fix this typescript error
style={style}
resizeMode={resizeMode}
onLoadEnd={onLoadEnd}
source={source}
/>
);
};
export default PlatformImage;
The mistake-prone import. Notice how vscode suggests a bunch of imports but we should only ever do import PlatformImage from '/ui/corex/PlatformImage';
Upvotes: 0
Reputation: 875
Lets say that your component name is ProgressBar, so create index.d.ts
and adjust your code below in index.d.ts
import {Component} from 'react';
import {Constructor, NativeMethodsMixin} from 'react-native';
export interface ProgressBarProps {
progress: number;
}
declare class ProgressBarComponent extends Component<ProgressBarProps> {}
declare const ProgressBarBase: Constructor<NativeMethodsMixin> &
typeof ProgressBarComponent;
export default class ProgressBar extends ProgressBarBase {}
So your component folder should looks like
component/
- index.android.tsx
- index.ios.tsx
- index.d.ts
Upvotes: 4
Reputation: 400
With last version of React you can use Suspense and lazy to avoid over typings etc., for example if I want a component Touchable with specific code for iOS and Android, my structure will looks like that:
- Touchable
- index.tsx
- Touchable.android.tsx
- Touchable.ios.tsx
- types.d.ts
And on index.tsx the code will looks like that:
import React, { FunctionComponent, lazy, Suspense } from 'react';
import { Platform, View } from 'react-native';
import { TouchableProps } from './types.d';
const TouchableComponent = lazy(() =>
Platform.OS === 'ios'
? import('./Touchable.ios')
: import('./Touchable.android'),
);
const Touchable: FunctionComponent<TouchableProps> = (props) => {
return (
<Suspense fallback={<View />}>
<TouchableComponent {...props} />
</Suspense>
);
};
export default Touchable;
So anywhere on my app I want to use this component, I just have to do that:
import Touchable from './path/to/Touchable';
[...]
<Touchable>
<Text>Touchable text</Text>
</Touchable>
[...]
Types.d.ts :
export type TouchableSizeType = 'small' | 'regular' | 'big';
export type TouchableType = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
export interface TouchableProps {
disabled?: boolean;
size?: TouchableSizeType;
type?: TouchableType;
onClick?: (event?: Event) => Promise<void>;
}
Upvotes: 8
Reputation: 638
One way of doing it, which is a bit annoying, is creating a declaration file with the same name.
component
- component.d.ts <---
- component.android.tsx
- component.ios.tsx
then
import { file } from "../group/file";
Update: Another way of doing it is just omit the extension for one of them, then typescript will pick up the default one.
Android will pick up the specific android file, and iOS will default to normal one.
Upvotes: 38
Reputation: 22872
It all makes sense, since file at ../component/component.tsx
does not exists.
Haven't tried, but this could learn TS to understand such imports
{
"compilerOptions": {
"paths": {
"*": ["*", "*.ios", "*.android"]
}
}
}
Upvotes: 0