We are trying to use the same React components both in mobile (react-native) and in web (create-react-app).
It's is working out pretty well so far thanks to react-native-web (configuration below).
However react-native-vector-icons, which we use extensively in the react-native project, does not compile with react-native-web. This means any component which uses a component with vector-icons will also need a specific web version. Having web specific versions of components effects maintenance.
Is there a known way, without having to eject create-react-app configuration, to deal with 3rd parties such as react-native-vector-icons in web?
import {createIconSetFromIcoMoon} from "react-native-vector-icons";
import Font from './Font.json';
export default createIconSetFromIcoMoon(UbeyaFont);
Things we've thought of so far:
we are currently looking into two ideas:
Environment based import:
# Pseudo code:
# if mobile import this, otherwise import that.
We're not sure whether this kind of dynamic importing is possible
Webpack configuration which is injected into node_modules/react-scripts. Not elegant but with a gulp watch which we have anyway we can make sure the configuration is always there.
Any ideas or thoughts are appreciated.
Configuration:
We've built a new private NPM package which holds all the React components and by using gulp watch that copies the package to both the mobile and web projects we save the trouble of constantly npm publishing and yarning during development (the only drawback Webstorm's indexing process).
We ended up using a gulp script to overwrite webpack. The stages are:
1) Build replacement packages
We built a web version for each React Native 3rd party. For react-native-vector-icons it turned out to be quite simple:
import React from 'react';
export const Icon = (props) => <div className="material-icons"
style={{color:props.color}}>
{props.name}
</div>;
export default Icon;
2) Adjust the Webpack configuration
Using gulp we overwrite the create-react-app webpack configuration with:
resolve: {
modules: ...
alias: {
"babel-runtime": path.dirname(
require.resolve("babel-runtime/package.json")
),
"react-native": "#ourcompany/react-native-web",
"react-native-vector-icons": "#ourcompany/react-native-vector-icons",
...
The gulp task script:
gulp.task("copyWebpack", function() {
return gulp
.src(["package.json", "src/_webpack/*.js"])
.pipe(
gulp.dest(
path.resolve(pathWeb + "/node_modules/react-scripts/config")
)
);
});
Note:
Ejecting create-react-app's configuration is cleaner, however it means you'll need to maintain the configuration and we preferred leaving the configuration as is and overwriting it during the build process.
You'd notice we have overwritten react-native-web itself. More on this in the next optional step.
3) Extend react-native-web (optional)
If you are using components which react-native-web does not yet support, you would want to build packages for them and extend react-native-web so your web version will work. Simply create a new package #yourcompany/react-native-web and generate an index.js in which import the components that do exist in react-native-web and reference the ones you've built. Note that when we built our project react-native-web didn't have a FlatList or a SectionList and now (October 2018) it has both (Modal is still missing :-)).
import {
StyleSheet as _StyleSheet,
View as _View,
Text as _Text,
Image as _Image,
I18nManager as _I18nManager,
AsyncStorage as _AsyncStorage,
Platform as _Platform,
Linking as _Linking,
ActivityIndicator as _ActivityIndicator,
ListView as _ListView,
Modal as _Modal,
Picker as _Picker,
RefreshControl as _RefreshControl,
TextInput as _TextInput,
Touchable as _Touchable,
TouchableHighlight as _TouchableHighlight,
TouchableNativeFeedback as _TouchableNativeFeedback,
TouchableOpacity as _TouchableOpacity,
TouchableWithoutFeedback as _TouchableWithoutFeedback,
Dimensions as _Dimensions,
Animated as _Animated,
ScrollView as _ScrollView,
SafeAreaView as _SafeAreaView,
BackHandler as _BackHandler,
Switch as _Switch,
NetInfo as _NetInfo,
AppState as _AppState,
ColorPropType as _ColorPropType,
} from 'react-native-web';
import {SectionList as _SectionList} from './SectionList';
import {FlatList as _FlatList} from './FlatList';
import {Alert as _Alert} from './Alert';
import {WebView as _WebView} from './WebView';
import {Share as _Share} from './Share';
import {PermissionsAndroid as _PermissionsAndroid} from './PermissionsAndroid';
import {ActionSheetIOS as _ActionSheetIOS} from './ActionSheetIOS';
import {ToastAndroid as _ToastAndroid} from './ToastAndroid';
export const StyleSheet = _StyleSheet;
export const View = _View;
export const Text = _Text;
export const Image = _Image;
export const I18nManager = _I18nManager;
export const AsyncStorage = _AsyncStorage;
export const Platform = _Platform;
export const Linking = _Linking;
export const ActivityIndicator = _ActivityIndicator;
export const ListView = _ListView;
export const Modal = _Modal;
export const Picker = _Picker;
export const RefreshControl = _RefreshControl;
export const TextInput = _TextInput;
export const Touchable = _Touchable;
export const TouchableHighlight = _TouchableHighlight;
export const TouchableNativeFeedback = _TouchableNativeFeedback;
export const TouchableOpacity = _TouchableOpacity;
export const TouchableWithoutFeedback = _TouchableWithoutFeedback;
export const Dimensions = _Dimensions;
export const Animated = _Animated;
export const ScrollView = _ScrollView;
export const SafeAreaView = _SafeAreaView;
export const BackHandler = _BackHandler;
export const Switch = _Switch;
export const NetInfo = _NetInfo;
export const AppState = _AppState;
export const Alert = _Alert;
export const Share = _Share;
export const WebView = _WebView;
export const PermissionsAndroid = _PermissionsAndroid;
export const ActionSheetIOS = _ActionSheetIOS;
export const ToastAndroid = _ToastAndroid;
export const SectionList = _SectionList;
export const FlatList = _FlatList;
export const ColorPropType = _ColorPropType;
Related
I already tried a well-known resolution of this problem--"change every file which imports from .env". It worked first few times, but now it has no effect.
I'd appreciate it if somebody shed some lights on my questions.
Why the solution stopped functioning?
Why it worked first few times?
Is there any other way to solve this problem?
.env
REACT_APP_API_BASE_URL=http://10.0.2.2:3000/v8/api
constants.js
import {REACT_APP_API_BASE_URL} from "#env"
export const apiBaseUrl = REACT_APP_API_BASE_URL
//I added this sentence so that my app recognizes changes in .env
App.js
import React,{useEffect} from 'react';
import {
Text,
View,
Alert
} from 'react-native';
import { apiBaseUrl } from './constants.js';
const App = () => {
useEffect(()=>{
Alert.alert(apiBaseUrl)
//The above alert should display http://10.0.2.2:3000/v8/api, but it displays the previous value of REACT_APP_API_BASE_URL
},[])
return (
<View>
<Text>Hey</Text>
</View>
);
};
export default App
I tried following solutions, but it didn't work;
restart metoro
rebuild project by npx react-native run-android
wipe data in ADV
I am trying to write a custom plugin for our Docusaurus site. I am able to wire up the custom component, but I cannot use hooks like useState or useEffect. The page crashes saying I'm using an invalid React hook.
I know its possible to use hooks because I see other plugins doing it so I'm sure its a syntax problem somewhere.
Here's my code:
index.ts
import path from 'path'
module.exports = function () {
return {
name: 'docusaurus-theme-myorg-technology',
getThemePath() {
return path.resolve(__dirname, './theme')
}
};
};
theme/index.tsx
import React from 'react'
import {CustomTOC} from './CustomTOC'
const WrappedTOC = (props: any) => {
return (
<CustomTOC {...props} />
);
};
export default WrappedTOC;
theme/CustomTOC.tsx
import React, { useState } from 'react';
import TOC from '#theme-init/TOC';
export default function CustomTOC(props: any) {
//const [tags, setTags] = useState<any[]>([]); <-- if I comment this out the page crashes
return (
<>
<TOC {...props} />
Hello world
</>
);
}
"Invalid hooks call" link to a doc page, that you should read carefully.
Most likely: you are using a different version of React for your component lib that the one Docusaurus uses internally, and it leads to the React lib being used twice at runtime. Make sure the final project will only include one React version. You can for example use the exact same version that the one Docusaurus uses
What is the simplest way to implement Multi-Platform Setup for a component in Expo. I have tried mamy diferent ways.. it was working on web but it is failing on Native and failing with Jest & #testing-library/react-native. Ideally I would like the least amount of custom config etc (do not want to eject). I expect the file structure to look like this:
Component
|- index.tsx
|- Component.native.tsx
|- Component.web.tsx
I am not sure how to do the index.tsx. I saw someone say something like this would work:
// index.tsx
// #ts-ignore
export { default } from "Component"
this didn't work so I did
// index.tsx
// #ts-ignore
export { default } from "./Component"
This worked for web, but the jest test said
Cannot find './Component'
However, Jest was able to find:
'./Component.mobile.tsx'
'./Component.web.tsx'
I tried:
// index.tsx
// #ts-ignore
import Component from "./Component";
export default Component
and the tests was the same
and the native emulator said:
Unable to resolve module ./Component
I tried using lazy loading but this does not work on web.
import { lazy, Suspense } from "react";
import { Platform } from "react-native";
import Loading from "../../components/Loading";
import { ComponentType } from "./types";
const Web = lazy(() => import("./Component.web"));
const Mobile = lazy(() => import("./Component.mobile"));
const Component: ComponentType = (props) => {
const isWeb = Platform.OS === "web";
return (
<Suspense fallback={<Loading message="Loading Component" />}>
{isWeb ? <Web {...props} /> : <Mobile {...props} />}
</Suspense>
);
};
export default Component
Questions
how to use diferent files for components depending on platform (exlude other files from build)
how to make it ok with ts in vscode
Using Expo 44. Thanks
I would use named exports. So begin by having the same component name in both files. Next I would have one file called Component.tsx and the other Component.native.tsx. That will be enough to allow the bundler to pull the native for native and the other for non-native (in other words web). That should be all you need.
I have created an react-native/typescript app with expo CLI, this generate some base code, inlcuding hooks/useCachedResources to load any resources or data that we need prior to rendering the app, in my case in this hook I load custom fonts(in particular Inter Display Font). I'm experimenting some problems because the app loads only two weights: regular and medium, If I try to use semi-bold or bold this doesnt work and use the san serif font that comes by default.
Additional data:
The fonts path its ok
Expo app doesn't show any error. I have seen in other questions errors such as fontFamily "MyFontFamily" is not a system font and has not been loaded through Font.loadAsync. This is not the case.
Font family name is in the correct format.
I'm using React Native UI Kitten and I load the fonts as they suggest in Advanced Configuration and change some especific styles.
According to some answers The out of the box support for custom fonts on Android is a little limited in React Native. It does not support font weights other than normal and bold. So I tried setting fontWeight: normal or any of the weights but nothing works.
useCachedResources hook
This come by default with expo init my-app.
import * as Font from 'expo-font';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useState } from 'react';
// Error reporting service
import { logger } from '#utils';
export function useCachedResources(): boolean {
const [isLoadingComplete, setLoadingComplete] = useState(false);
// Load any resources or data that we need prior to rendering the app
useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
await SplashScreen.preventAutoHideAsync();
// Load fonts
await Font.loadAsync({
'inter-display-regular': require('../assets/fonts/InterDisplay-Regular.ttf'),
'inter-display-medium': require('../assets/fonts/InterDisplay-Medium.ttf'),
'inter-display-semibold': require('../assets/fonts/InterDisplay-SemiBold.ttf'),
'inter-display-bold': require('../assets/fonts/InterDisplay-Bold.ttf'),
});
} catch (loadCachedResourcesError) {
logger.log(loadCachedResourcesError);
} finally {
setLoadingComplete(true);
await SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);
return isLoadingComplete;
}
Consuming the hook in App.tsx
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import useCachedResources from './hooks/useCachedResources';
import Navigation from './navigation';
// again this comes by defautl expo init command
export default function App(): React.ReactElement | null {
const isLoadingComplete = useCachedResources();
if (!isLoadingComplete) {
return null;
}
return (
<SafeAreaProvider>
<Navigation />
<StatusBar />
</SafeAreaProvider>
);
}
mapping.json: specific UI-Kitten configuration to change font style
I can think that the problem comes from here but the thing is, if there was a problem loading the fonts, either expo would have already thrown an error or the other fonts weights(regular/medium) would not load.
{
"strict": {
"text-font-family": "inter-display-regular",
"text-heading-1-font-size": 32,
"text-heading-1-font-weight": "normal",
"text-heading-1-font-family": "inter-display-medium",
"text-paragraph-1-font-size": 16,
"text-paragraph-1-font-weight": "normal",
"text-paragraph-1-font-family": "$text-font-family",
}
}
The problem
I have no idea if the problem comes from expo, ui kitten or if inter font can't be loaded by react native by some other reason.
In your useCachedResources try to remove 'await' keyword from SplashScreen method's:
SplashScreen.preventAutoHideAsync();
SplashScreen.hideAsync();
I was wondering how I would render some Shoutem extension, for simplicity I am going to render it as my only component like so:
import 'es6-symbol/implement';
import React from 'react';
import {
AppRegistry,
View
} from 'react-native';
import { AppBuilder } from '#shoutem/core';
import { NavigationBar } from '#shoutem/ui';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import extensions from './extensions.js';
import { screens } from './extensions/kevinyclu.restaurants/app/index';
const List = screens.List;
const store = createStore((state, action) => state);
const App = () => <Provider store={store}><View><List /></View></ Provider>;
// noinspection JSCheckFunctionSignatures
AppRegistry.registerComponent('Restaurant', () => App);
But this gives me an error that says:
Though if I replace the const App = ... with the code that was initially there when I did shoutem configure
const App = new AppBuilder()
.setExtensions(extensions)
.setRenderNavigationBar(renderNavigationBar)
.build();
Then everything works fine, so I was wondering how would I use a Shoutem extension? Or am I missing the point of the extension completely?
You simply add it in the Builder by adding a screen. The flow is explained in our getting started docs. You create an extension, create a screen with a shortcut and then upload it to the Shoutem servers and install it in one of your apps on the Builder.
After that, you can go to the app in the Builder and add that new extension's screen by clicking the + button next to Screens. You can easily find your new extension by selecting the Custom category.
Remember that after installing a new app, you should run shoutem configure in the cloned app's directory. This will set up the new configuration you have after you've installed a new extension on the Builder.
Some advice; if you ever uninstall an extension on the Builder, it's good to re-clone your app completely, because shoutem configure will not remove the extension's from the directory, which may "hide" errors. For example, you could be importing something from that extension that you uninstalled, but you won't get an error because the files are all still there, even though they're uninstalled.