Skip to content

RonasIT/react-native-common-modules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React Native Common Modules

Common components for Ronas IT projects.

Start the example app

  1. Install dependencies: npm install
  2. Start app for local development: cd apps/example && npx expo start
  3. Use Expo Go to run mobile version

Usage

  1. Install the package: npm i @ronas-it/react-native-common-modules
  2. Import modules to your app and use as described below

UI-components

At the moment this library contains the following components:

1. AppPressable

This component can be used in the same way as the built-in Pressable component, but it also includes opacity control.

Props:

  • pressedOpacity: Opacity value. Default is 0.4.

Example:

import { AppPressable } from '@ronas-it/react-native-common-modules';

<AppPressable style={styles.button} pressedOpacity={0.5}>
  <Text>Press Me</Text>
</AppPressable>

2. AppSafeAreaView

A component for granular control of safe area edges on each screen. The difference from SafeAreaView in react-native-safe-area-context is that the container adds padding to the elements inside it, rather than to the entire screen, making it more flexible for use.

Props:

  • edges: An array indicating which edges of the screen to respect. Possible values are 'top', 'right', 'bottom', 'left'. Defaults to all edges.
  • style: Custom styles to apply to the view. Note that padding values will be adjusted to respect safe area insets.

Example:

import { AppSafeAreaView } from '@ronas-it/react-native-common-modules';

<AppSafeAreaView edges={['top', 'bottom']} style={styles.container}>
  <Text>Content goes here</Text>
</AppSafeAreaView>

3. VirtualizedList

A component-wrapper for FlashList, that includes onScrollUp and onScrollDown props.

Props:

  • onScrollUp: Called when user scrolls up.
  • onScrollDown: Called when user scrolls down.

NOTE: onScrollUp and onScrollDown are synced with onScroll

Example:

import { VirtualizedList, VirtualizedListProps } from '@ronas-it/react-native-common-modules';

export function App(): ReactElement {
  const [direction, setDirection] = useState<'UP' | 'DOWN'>();

  const handleScrollUp = (): void => {
    setDirection('UP');
  };

  const handleScrollDown = (): void => {
    setDirection('DOWN');
  };

  const handleScrollEnd = (): void => {
    setDirection(undefined);
  };

  const renderItem: VirtualizedListProps<Book>['renderItem'] = ({ item }) => {
    return (
      <View>
        <Text>{item.isbn}</Text>
        <Text>{item.title}</Text>
      </View>
    );
  };

  const keyExtractor: VirtualizedListProps<Book>['keyExtractor'] = (item) => item.isbn;

  return (
    <View>
      <Text>Direction: {direction}</Text>
      <VirtualizedList
        estimatedItemSize={86}
        data={books}
        renderItem={renderItem}
        onScrollUp={handleScrollUp}
        onScrollDown={handleScrollDown}
        onScrollEndDrag={handleScrollEnd}
        onMomentumScrollEnd={handleScrollEnd}
        keyExtractor={keyExtractor}
      />
    </View>
  );
}

Services

1. Push notifications

PushNotificationsService

Service for integrating Expo push notifications into apps. Requires setup and backend implementation for sending notifications.

PushNotificationsService public methods:

  • obtainPushNotificationsToken - get an Expo token that can be used to send a push notification to the device using Expo's push notifications service.

  • pushToken - getter for retrieving the token if it was already obtained.

usePushNotifications

Hook, that automatically subscribes the device to receive push notifications when a user becomes authenticated, and unsubscribes when a user becomes non-authenticated. It supports custom subscription and unsubscription logic through provided functions or API configuration. Listens for responses to notifications and executes a callback, if provided, when a notification is interacted with. Used in the root App component.

usePushNotifications hook arguments:

  • isAuthenticated (required) - flag, that indicates whether the user is authenticated or not.
  • onNotificationResponse (optional) - callback when a notification is interacted with.
  • subscribeDevice (optional) - function for subscribing the device.
  • unsubscribeDevice (optional) - function for unsubscribing the device.
  • apiConfig (optional) - API configuration for subscribing and unsubscribing the device (when subscribeDevice and unsubscribeDevice are not provided).
  • apiErrorHandler (optional) - API error handler for subscribe/unsubscribe functions.
  • getTokenErrorHandler (optional) - handler for error that occur when attempting to obtain a push notifications token.

Example:

// Somewhere in a root component of your app:
import { usePushNotifications } from '@ronas-it/react-native-common-modules';

...
const authToken = useSelector(authSelectors.token);
...
usePushNotifications({
  apiConfig: {
    subscribeDeviceUrl: 'https://your-api.com/api/v1/push-notifications/subscribe',
    unsubscribeDeviceUrl: 'https://your-api.com/api/v1/push-notifications/unsubscribe',
    accessToken: authToken,
  },
  isAuthenticated: !!authToken,
})

2. Storage

A library that provides two types of key-value storage API: AsyncStorage and SecuredStorage (IOS, Android).

Example

Implement storage service:

import { AsyncStorageItem, SecureStorageItem } from '@ronas-it/react-native-common-modules';

class AppStorageService {
  public token = new SecureStorageItem('token');
  public tokenExpiryDate = new SecureStorageItem('tokenExpiryDate');
  public language = new AsyncStorageItem('language');
}

export const appStorageService = new AppStorageService();

Usage:

// Get storage item
const token = await appStorageService.token.get();
// Set storage item
appStorageService.token.set('new_token');
// Delete storage item
appStorageService.token.remove();

3. Image Picker

ImagePickerService gives the application access to the camera and image gallery.

Public methods:

  • getImage - initializes the application (camera or gallery) and returns a result containing an image.
  • launchGallery - launches the gallery application and returns a result containing the selected images.
  • launchCamera - launches the camera application and returns the taken photo.
  • requestGalleryAccess - requests the application access to the gallery.
  • requestCameraAccess - requests the application access to the camera.
  • getFormData - creates a FormData object with image.

Example

Pick image and send request:

import { imagePickerService, ImagePickerSource } from '@ronas-it/react-native-common-modules';

const handlePickImage = async (source: ImagePickerSource) => {
  const image = await imagePickerService.getImage(source);
  const asset = image?.assets?.[0];

  if (!asset) {
    return;
  }

  const data = imagePickerService.getFormData(asset.uri);

  // API call
  createMedia(data);
};

4. WebSocket

WebSocketService manages WebSocket connections using Pusher and can work in both web and mobile applications. Doesn't support Expo Go.

It's necessary to install @pusher/pusher-websocket-react-native for a mobile app and pusher-js for a web app.

Options for WebSocketService constructor:

  • apiKey (required) - APP_KEY from Pusher Channels Dashboard.
  • cluster (required) - APP_CLUSTER from Pusher Channels Dashboard.
  • authURL (optional) - a URL that returns the authentication signature needed for private channels.
  • useTLS (optional) - a flag that indicates whether TLS encrypted transport should be used. Default value is true.
  • activityTimeout (optional) - time in milliseconds to ping a server after last message.
  • pongTimeout (optional) - time in milliseconds to wait a response after a pinging request.

WebSocketService public methods:

  • connect initializes and connects the Pusher client. Optional authorization token is used for secure connections.
  • subscribeToChannel subscribes to a specified channel and registers an event listener for incoming messages on that channel.
  • unsubscribeFromChannel removes an event listener and, if there is no listeners for a specified channel, unsubscribes from it.

Example:

import { WebSocketService } from '@ronas-it/react-native-common-modules';

// Create a service instance
type ChannelName = `private-conversations.${number}` | `private-users.${number}`;
const webSocketService = new WebSocketService<ChannelName>({
  apiKey: '1234567890qwertyuiop',
  cluster: 'eu',
  authURL: 'https://your-api.com/api/v1/broadcasting/auth'
});

// Initialize Pusher, e.g. after an app initialization or successful authorization
await webSocketService.connect('your-auth-token');

// Subscribe to a channel when it's necessary
webSocketService.subscribeToChannel('private-conversations.123', (event) => {
  console.log('Received event:', event);
});

// Unsubscribe from a channel, e.g. before an app closing or logging out
webSocketService.unsubscribeFromChannel('private-conversations.123', (event) => {
  console.log('Received event:', event);
});

Utils

1. setupReactotron(projectName: string)

Configures and initializes Reactotron debugger with redux plugin for development purposes. Install the Reactotron app on your computer for use.

Example:

import { createStoreInitializer } from '@ronas-it/rtkq-entity-api';
import { setupReactotron } from '@ronas-it/react-native-common-modules';

const reactotron = setupReactotron('your-app');
const enhancers = reactotron ? [reactotron.createEnhancer()] : [];

const initStore = createStoreInitializer({
  rootReducer: rootReducer as unknown as Reducer<AppState>,
  middlewares,
  enhancers,
});

2. i18n

Provides functions to set language and use translations using i18n-js

Example:

root layout:

import { setLanguage } from '@ronas-it/react-native-common-modules';

const translations = {
  en: {
    ...require('i18n/example/en.json')
  },
  fr: {
    ...require('i18n/example/fr.json')
  }
};

const useLanguage = setLanguage(translations, 'en');

interface LanguageContextProps {
  language: string;
  onLanguageChange?: (language: keyof typeof translations) => void;
}

export const LanguageContext = createContext<LanguageContextProps>({ language: 'en' });

function App(): ReactElement {
  return (
    <Stack>
      <Stack.Screen name='index' />
    </Stack>
  );
}

export default function RootLayout(): ReactElement | null {
  const [language, setLanguage] = useState<keyof typeof translations>('en');

  useLanguage(language);

  return (
    <LanguageContext.Provider value={{ language, onLanguageChange: setLanguage }}>
      <App />
    </LanguageContext.Provider>
  );
}

screen:

import { AppPressable, AppSafeAreaView, useTranslation } from '@ronas-it/react-native-common-modules';
import { ReactElement, useContext } from 'react';
import { View, Text, Alert } from 'react-native';
import { LanguageContext } from './_layout';

export default function RootScreen(): ReactElement {
  const translate = useTranslation('EXAMPLE');
  const { language, onLanguageChange } = useContext(LanguageContext);

  const onPress = () => Alert.alert(translate('TEXT_PRESSED'));

  const handleLanguageChange = (): void => {
    onLanguageChange?.(language === 'en' ? 'fr' : 'en');
  };

  return (
    <AppSafeAreaView edges={['bottom']} style={styles.safeAreaContainer}>
      <View style={styles.container}>
        <AppPressable onPress={onPress} hitSlop={10}>
        <Text>{translate('BUTTON_PRESS_ME')}</Text>
        </AppPressable>
        <AppPressable onPress={handleLanguageChange} hitSlop={10}>
          <Text>{translate('BUTTON_LANGUAGE')}</Text>
        </AppPressable>
      </View>
    </AppSafeAreaView>
  );
}