Only this pageAll pages
Powered by GitBook
1 of 83

BlueBase

Loading...

Tutorial

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Key Concepts

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

API

Loading...

Loading...

Loading...

Loading...

Guides

Loading...

Loading...

Components

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

2.3 Generate Typescript Interfaces

The last step is to generate typescript interfaces from our GraphQL schema. This step is used to enhance the developer experience to strongly type our API.

1. Add dependencies

Install the following dev dependencies:

yarn add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations graphql --dev

2. Make your API accessible

Add the following script to the script section of your package.json file:

3. Create the codegen config file

Create a file codegen.yml in the root directory:

Replace [[API_URL]] and [[API_KEY]] with the values relevant to your project.

  "graphql-codegen": "graphql-codegen",
name: GraphQL API
schema:
  - [[API_URL]]/graphql/v1:
      headers:
        apiKey: "[[API_KEY]]"
documents: ./src/**/*.graphql.ts
generates:
  ./src/graphql-types.ts:
    plugins:
      - typescript
      - typescript-operations

3.1 Pending Tasks Screen

Ok, so now with the configurations out of the way, it's time to start coding our app. The first thing we need to do is to create an application skeleton by developing empty screens and configuring the navigation mechanism.

The first screen that we are going to create is going to show a list of pending tasks.

Step 1: Create Screen View

Let's start by creating our screen view component. Let's call it PendingTasksScreen. Feel free to copy the code below.

src/screens/PendingTasksScreen/PendingTasksScreen.tsx
import React from 'react';
import { Text, View } from 'react-native';

export const PendingTasksScreen = () => {
	return (
		<View style={{ padding: 10 }}>
			<Text>PendingTasksScreen</Text>
		</View>
	);
};

PendingTasksScreen.displayName = 'PendingTasksScreen';

All we're doing right now is rendering the component name. Don't worry we will add the actual list later.

Also, create the following index file:

And also:

Step 2: Create Route

BlueBase provides an abstraction to create navigation plugins. This allows us to be able to use any navigation library in our app. For now, we have created & integrations.

The navigation API is loosely based on . It is very simple to create your own routes. Just add the routes property to your plugin. This is an array of objects:

Explanation of the code above:

  • Line 12: We changed the plugin's index route to the new one created at Line 23. By doing this, whenever we press the task icon on our launcher, we get navigated to this screen.

  • Line 19: Notice we added the PendingTasksScreen component to BlueBase. This is so we can reference it in our route configuration at Line 24. This is where we tell BlueBase what component to render once we have navigated to that route.

  • Line 23: Name of the route. We use this name to navigate to this screen. We will see an example of this later.

That's all. Run your app, and test your new screen.

  • Line 25: Path of the screen. This is used to create the URL for the web. The URL pattern is [[host]]/p/[[plugin-key]]/[[route-path]]. Since this is the plugin home page, we leave it empty. Please see the URL in the browser screenshot for an example.

  • Line 28: Route options, like title, etc.

  • React Navigation
    React Router
    React Navigation V5
    Web
    iOS
    src/screens/PendingTasksScreen/index.ts
    export * from './PendingTasksScreen';
    
    import { PendingTasksScreen } from './PendingTasksScreen';
    export default PendingTasksScreen;
    src/screens/index.ts
    import { PendingTasksScreen } from './PendingTasksScreen';
    
    export const screens = {
    	PendingTasksScreen,
    };
    import { createPlugin } from '@bluebase/core';
    import { ToDoAppIcon } from './components/ToDoAppIcon';
    import { PendingTasksScreen } from './components/PendingTasksScreen';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	// ... Other plugin properties
    
    	indexRoute: 'TasksApp',
    	
    	components: {
    		// Components
    		ToDoAppIcon,
    
    		// Screens
    		PendingTasksScreen,
    	},
    
    	routes: [{
    		name: 'TasksApp',
    		screen: 'PendingTasksScreen',
    		path: '',
    		exact: false,
    
    		options: {
    			title: 'My Tasks',
    		},
    	}]
    });

    Consuming Selected Theme

    BlueBase exports a ThemeConsumer component to utilise current theme. This is a render prop component, which means it takes a function as a child. The function gets an object in the param with the following interface:

    interface ThemeContextData {
    
        // Helper method to change current theme.
        changeTheme: (slug: string) => void,
    
        // Current theme
        theme: Theme
    }

    Usage

    The following snippet illustrates how to use a ThemeConsumer component.

    const ThemedComponent = () => (
        <ThemeConsumer>
        {
            ({ theme, changeTheme }: ThemeContextData) => {
                // Use theme context here..
            }
        }
        </ThemeConsumer>
    );

    Example: ThemePicker

    The following example shows how to use ThemeConsumer component to create a theme picker. This renders a picker component with a list of all installed themes. It not only uses the current theme to style itself, but also utilises the changeTheme method to switch themes.

    import {
        BlueBase,
        BlueBaseContext,
        ThemeConsumer,
        ThemeContextData,
        Text,
        View,
    } from '@bluebase/core';
    import { Picker } from 'react-native';
    import React from 'react';
    
    export class ThemePicker extends React.PureComponent {
    
        static contextType = BlueBaseContext;
    
        render() {
            const BB: BlueBase = this.context;
            const themes = [...BB.Themes.entries()];
            return (
                <ThemeConsumer children={({ theme, changeTheme }: ThemeContextData) => (
                    <View style={{ backgroundColor: theme.palette.background.default }}>
                        <Text>Select Theme</Text>
                        <Picker
                            selectedValue={BB.Configs.getValue('theme.name')}
                            style={{ width: 150 }}
                            onValueChange={changeTheme}>
                            {themes.map(entry => <Picker.Item label={entry[1].name} value={entry[0]} key={entry[0]} />)}
                        </Picker>
                    </View>
                )} />
            );
        }
    }

    Logger

    BlueBase's Logger API allows you to log messages to any number of logging services.

    Working with the API

    You can call logger for different console message modes:

    BB.Logger.log('log bar');
    BB.Logger.info('info bar');
    BB.Logger.debug('debug bar');
    BB.Logger.warn('warn bar');
    BB.Logger.error('error bar');

    When handling an error:

    try {
        ...
    } catch(e) {
        BB.Logger.error('error happened', e);
    }

    Integrations

    To add support for a Logging Provider see this .

    guide

    PluginIcon 📌

    Displays an icon of a Plugin. The icon properties are taken from plugin.icon property of plugin.

    • If no plugin is found, renders an error message.

    • If a plugin has no icon, renders null.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    id

    string

    yes

    -

    Plugin key

    size

    number

    -

    -

    Icon size

    import { PluginIcon } from '@bluebase/core';
    
    // Then somewhere in your app:
    <PluginIcon id="redux-plugin" />

    Noop 📌

    A component that does... nothing!

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    A more practical usage of this is as a fallback component:

    If none of the components key are found in the Component Registry, and error is thrown. To avoid this, just use Noop as that last option. So in the example above, if Option1 and Option2 are not found, Noop renders null instead.

    import { Noop } from '@bluebase/core';
    
    // Then somewhere in your app:
    <Noop />
    const CustomComponent = BB.Components.resolve('Option1', 'Option2', 'Noop');

    ErrorObserver 📌

    Observes any exceptions in child tree hierarchy. When an exception is caught, displays an Error state to gracefully handle it on the frontend.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    errorComponent

    Component

    no

    -

    Component to show the error state.

    children

    ReactNode | (() => ReactNode)

    no

    -

    Children are rendered when there are no error.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    error

    Error

    no

    -

    If an error is passed as a prop, shows an error state.

    checkError

    (props) => Error

    no

    -

    A function to check error based on props.

    <ErrorObserver>
     <Text>Rendered if there is no error here</Text>
    </ErrorObserver>

    View

    Introduction

    BlueBase is a framework built on top of React & React Native. It provides an opinionated application structure to create cross-platform native and web apps using the same code base.

    Apart from this, BlueBase comes with a lot of features that are essential to all modern apps:

    🔌 Plug-ins

    Create modular applications by breaking different features into independent plug-ins. Plug-ins are powerful enough to enhance or modify any part of the application and can be hosted in their own repositories. This also allows code reuse across applications.

    🎁 Components

    Create pluggable components that can be dynamically replaced or modified through third-party plugins, without needing to change the original code. This makes it extremely easy to enhance existing views or screens in the app by writing your own plugin.

    🚏 Routes

    Create cross-platform routes to navigate between screens by using a simple JSON syntax.

    ⚙️ Configurations

    BlueBase provides API to get or set application-level configurations. This allows developers or even end-users to change app settings.

    🎨 Themes

    A theme is a collection of styles that's applied to an entire app or component. When you apply a theme, every component in the app applies each of the theme's attributes that it supports. Developers can create their own themes as well as allow end-users to select one to their own liking.

    🎡 Lifecycle events

    Your application can use lifecycle hook methods to tap into key events in the lifecycle of an application to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up before reboot.

    💬 Localization

    Use the localization feature to translate and adapt your app into multiple languages and regions. Create multi-lingual and even right to left apps by using the simple to use API.

    🌚 Dark mode

    The Dark Mode is a first-class citizen in BlueBase and the feature comes out of the box 😎.

    🖼 Asset management

    Add dynamic Images and assets (i.e fonts, sounds, etc), to your app that may be replaceable through plugins. Moreover use dynamic images that render different versions on mobile, desktop, light, and dark modes.

    2. Backend API

    1. Getting Started

    1.1 Setup

    We will learn concepts of BlueBase by creating a production-ready To-Do app.

    We will use Expo CLI to create a new project and add BlueBase to it. If Expo CLI is not installed, go to the Expo CLI Installation guide before proceeding.

    Step 1: Initialize the project

    Initialize an expo app by executing the following commands:

    # Create a project named my-app.
    # Select the "blank (Typescript)" template when prompted
    expo init bluebase-todo-app
    
    # Navigate to the project directory
    cd bluebase-todo-app

    More info here.

    Step 2: Install BlueBase package

    Step 3: Update App File

    Step 4: Run App

    This will open the expo console.

    You can launch the web or native version of the app from here.

    Web
    iPhone
    yarn add @bluebase/core
    App.tsx
    import React from 'react';
    import { BlueBaseApp } from '@bluebase/core';
    
    export default function App() {
      return (
        <BlueBaseApp />
      );
    }
    expo start

    1.2 Add Plugins

    All functionality in BlueBase is added via plugins. Let us add some ready-made plugins to our project to take a head start on building our app.

    Step 1: Install plugins

    Start by adding following devDependencies:

    Some of the plugins (react-navigation) require some more dependencies to be installed:

    Step 2: Create plugin files

    Now in the root folder create the following file plugins.ts:

    The file above exports an array of plugins, imported from their respective modules.

    Create another file similar to the one above and name it plugins.native.ts. This way react-native compiler will load this version rather than plugins.ts on native platforms.

    So by doing this, we can add web-specific plugins in plugins.ts and native specific code in plugins.native.ts.

    In this case, the difference in the above 2 files is that plugins.ts uses:

    • @bluebase/plugin-react-router

    • @bluebase/plugin-material-ui

    While plugins.native.ts uses the following instead:

    • @bluebase/plugin-react-navigation

    • @bluebase/plugin-react-native-paper

    Step 3. Import plugins

    Now edit the App.tsx file and add the following content.

    Here, we import the plugins array and pass them to the BlueBaseApp component as a prop.

    Step 4: Add Babel plugin

    Lastly, modify the contents of babel.config.js file to:

    Step 5: Run

    Now run the app again:

    You should see the following screen on launch:

    Home Screen

    Settings App

    Explanation

    So as you can see, by just installing a few plugins, we were able to add a lot of functionality in a very short span of time, by writing very few lines of code.

    I'll try to briefly describe what purpose each plugin serves.

    Plugin
    Purpose

    yarn add @bluebase/plugin-launcher @bluebase/plugin-material-ui @bluebase/plugin-react-native-paper @bluebase/plugin-responsive-grid @bluebase/plugin-vector-icons @bluebase/plugin-settings-app @bluebase/plugin-react-router @bluebase/plugin-react-navigation @bluebase/plugin-responsive-grid @bluebase/plugin-json-schema-components
    expo install react-native-safe-area-context react-native-gesture-handler react-native-reanimated

    4. CRUD Operations

    3. Create Screens

    Provides Material UI components on native. Uses the library.

    @bluebase/plugin-react-navigation

    Provides Navigation capability on native. Uses the library.

    @bluebase/plugin-responsive-grid

    Provides Grid components (Column, Row, etc). Used by Launcher plugin.

    @bluebase/plugin-vector-icons

    Provides SVG vector icons used on various screens, as shown above. Uses the library.

    @bluebase/plugin-launcher

    Adds Android-like "launcher" home screen, that shows app icons.

    @bluebase/plugin-settings-app

    Adds the Settings section to the app. If you remove this plugin, the settings icon on the home screen will disappear.

    @bluebase/plugin-json-schema-components

    Provides components that help us build layouts by writing JSON. This is used by the settings plugin.

    @bluebase/plugin-material-ui

    Provides Material UI components on the web. Uses the MUI library.

    @bluebase/plugin-react-router

    Provides Navigation capability on the web. Uses the React Router library.

    Web
    iOS
    Web
    iOS

    @bluebase/plugin-react-native-paper

    1.3 Create Custom Plugin

    By now it should be clear that all functionality in BlueBase is added via plugins. So in order to add views to our To-Do app, we need to create our own plugin.

    Step 1: Install components library

    Start by adding the following dependency:

    This library provides typescript typings of components imported from the BlueBase context.

    Step 2: Create a plugin icon

    The first thing that we will do is to create a plugin icon component. Create the following files:

    The code above uses the BlueBase theme syntax to customize styles. Read more about it in the .

    Step 3: Create plugin

    Time to create the plugin itself. Create the following file:

    In the above code, we use the createPlugin function from the the @bluebase/core library. This function takes attributes as input, and returns a BlueBase Plugin.

    • The key property servers as the plugin ID, as well as the slug in the plugin URL path. So in this case the path to access this plugin would be: http://localhost:19006/p/tasks.

    • The name and description properties are pretty self explanatory. The name property is rendered below the icon on the home screen.

    Step 4: Use the plugin

    Now we're all set to use our shiny new plugin Edit our plugin files to add our own plugin to the list.

    Step 5: Run

    Now run the app again:

    You should see the following screen on launch:

    2.1 Create Backend API

    Before we start creating our app, we first need to create an API connected to a database. For this purpose, we will use a free tool called .

    Step 1: Sign up

    Go to and Sign up to create a new account.

    2.2 Setup Apollo Client

    To consume our GraphQL API on the app, we will be using the popular Graphql Client. Luckily, we already have an Apollo plugin available for BlueBase that takes care of all the setup.

    Step 1: Install the BlueBase Apollo plugin

    3.3 Task Create Screen

    We also need a screen to create a new task.

    Step 1: Create Route

    Similar to the previous section, now we create our third route. Notice that I have now moved the routes array dedicated file, to keep to code readable.

    Import this array to your plugin file:

    This should create the third route, which should be accessible via the following URL:

    3.4 Tab Navigation

    To improve the user experience of our app, we want to add tabs on the main page. One tab will show all pending tasks, while the other one will show the completed tasks.

    Step 1: Add Tab Navigator

    It is possible to add nested routes in BlueBase. Whenever we want to do this, we will add a navigator property in the route. There are several navigators available to use (i.e. switch, stack, tab

    3.2 Edit Task Screen

    Next up, we are going to create a screen that will allow a user to edit a task.

    Step 1: New Screens

    Before we move forward to adding new routes, let's quickly create 3 copies of our PendingTasksScreen component, and give them the following names:

    4.2 Reading Tasks

    It's time to query our API for all pending and completed tasks and display them as a list.

    Step 1: Task List Item

    The first step is to create a list item that will render a single task.

    Let's create a new component called TaskListItem . This component will have 2 states:

    plugins.ts
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
    import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    export const plugins = [
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginMaterialUI,
    	BlueBasePluginReactRouter,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    	BlueBasePluginSettingsApp,
    ];
    plugins.native.ts
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
    import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    export const plugins = [
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginReactNativePaper,
    	BlueBasePluginReactNavigation,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    	BlueBasePluginSettingsApp,
    ];
    App.tsx
    import 'react-native-gesture-handler';
    
    import React from 'react';
    import { BlueBaseApp } from '@bluebase/core';
    import { plugins } from './plugins';
    
    export default function App() {
      return (
        <BlueBaseApp plugins={plugins} />
      );
    }
    babel.config.js
    module.exports = function(api) {
      api.cache(true);
      return {
        presets: ['babel-preset-expo'],
        plugins: ['react-native-reanimated/plugin'],
      };
    };
    expo start
    yarn add @bluebase/components
    React Native Paper
    React Navigation
    React Native Vector Icons

    The components property is a Map of key, value pair. Where key is a string, and value is a React component. This map is used by BlueBase to store these components in its context. These can be fetched dynamically at a later stage, and can be overwritten by plugins to change the plugin implementation. More on this here. We save the ToDoAppIcon component in the example above.

  • The icon property defines the plugin icon that is rendered on the home page. In the icon.component sub-property tells which component to render. We use 'ToDoAppIcon' value, which is the same as the key on the component object in Line 10. So, by doing this we are telling BlueBase to render a component by the key ToDoAppIcon .

  • Lastly, the indexRoute key tells BlueBase which screen to navigate to when the icon is pressed. We tell it to go to HomeScreen here. Since we would already be on the HomeScreen so pressing this button will not do anything.

  • Themeing Chapter
    Web
    iOS
    Step 2: Use the plugin

    Same as the last time, we import the apollo plugin:

    Step 3: Create Apollo Configs

    Now we need to input our API URL as well as the API Token into the Apollo client. We do this by using BlueBase Configs.

    Create the following file in the root folder:

    Replace [[API_URL]] and [[API_KEY]] with the values relevant to your project.

    Step 4: Use the configs

    Similar to how we use the plugins, we need to pass our configs object as a prop to the BlueBaseApp component.

    Change the contents of the App.tsx to match below:

    Step 5: Bypass Apollo Bug

    At the time of writing this tutorial, the latest version of Apollo Client (v3.5.4) has compatibility an issue with react-native. Add the following file in the root folder to bypass it.

    This is it. Our App is configured to use the GraphQL API through the Apollo client.

    Apollo
    src/components/ToDoAppIcon/ToDoAppIcon.tsx
    import { DynamicIcon, View } from '@bluebase/components';
    import { useStyles, useTheme } from '@bluebase/core';
    import React from 'react';
    import { TextStyle, ViewStyle } from 'react-native';
    
    export interface ToDoAppIconStyles {
    	iconColor: { color: TextStyle['color'] };
    	root: ViewStyle;
    }
    
    export interface ToDoAppIconProps {
    	size: number;
    	styles?: Partial<ToDoAppIconStyles>;
    }
    
    export const ToDoAppIcon = (props: ToDoAppIconProps) => {
    	const { size } = props;
    	const { theme } = useTheme();
    
    	const styles: ToDoAppIconStyles = useStyles('ToDoAppIcon', props, {
    		iconColor: {
    			color: theme.palette.error.contrastText,
    		},
    		root: {
    			backgroundColor: theme.palette.error.main,
    			borderRadius: theme.shape.borderRadius * 3,
    			alignItems: 'center',
    			justifyContent: 'center',
    			height: size,
    			width: size,
    		},
    	});
    
    	return (
    		<View style={styles.root}>
    			<DynamicIcon
    				type="icon"
    				name="checkbox-multiple-marked-outline"
    				color={styles.iconColor.color}
    				size={size * 0.75}
    			/>
    		</View>
    	);
    };
    
    ToDoAppIcon.defaultProps = {
    	size: 100,
    };
    src/components/ToDoAppIcon/index.tsx
    export * from './ToDoAppIcon';
    
    import { ToDoAppIcon } from './ToDoAppIcon';
    export default ToDoAppIcon;
    src/components/index.tsx
    import { ToDoAppIcon } from './ToDoAppIcon';
    
    export const components = {
    	ToDoAppIcon,
    };
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    import { ToDoAppIcon } from './components/ToDoAppIcon';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	components: {
    		ToDoAppIcon,
    	},
    	
    	icon: {
    		component: 'ToDoAppIcon',
    		type: 'component',
    	},
    
    	indexRoute: 'HomeScreen',
    });
    plugins.ts
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
    import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginMaterialUI,
    	BlueBasePluginReactRouter,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    plugins.native.ts
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
    import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginReactNativePaper,
    	BlueBasePluginReactNavigation,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    expo start
    yarn add --dev @apollo/client @bluebase/plugin-apollo graphql
    plugins.ts
    import BlueBasePluginApollo from '@bluebase/plugin-apollo';
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
    import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginApollo,
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginMaterialUI,
    	BlueBasePluginReactRouter,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    plugins.native.ts
    import BlueBasePluginApollo from '@bluebase/plugin-apollo';
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
    import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginApollo,
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginReactNativePaper,
    	BlueBasePluginReactNavigation,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    
    configs.ts
    export const configs = {
    
    	// Apollo Graphql Configs
    	'plugin.apollo.httpLinkOptions': {
    		uri: [[API_URL]] + '/graphql/v1',
    		headers: {
    			apiKey: [[API_KEY]]
    		}
    	},
    
    };
    App.tsx
    import 'react-native-gesture-handler';
    
    import { BlueBaseApp } from '@bluebase/core';
    import React from 'react';
    
    import { configs } from './configs';
    import { plugins } from './plugins';
    
    export default function App() {
    	return (
    		<BlueBaseApp configs={configs} plugins={plugins} />
    	);
    }
    
    metro.config.js
    // Learn more https://docs.expo.io/guides/customizing-metro
    
    const { getDefaultConfig } = require('@expo/metro-config');
    
    const config = getDefaultConfig(__dirname);
    
    // https://github.com/facebook/metro/issues/535
    // https://github.com/apollographql/apollo-client/releases/tag/v3.5.4
    config.resolver.sourceExts = process.env.RN_SRC_EXT
    	? [...process.env.RN_SRC_EXT.split(',').concat(config.resolver.sourceExts), 'cjs'] // <-- cjs added here
    	: [...config.resolver.sourceExts, 'cjs']; // <-- cjs added here
    
    // https://docs.expo.dev/bare/installing-updates/
    config.transformer.assetPlugins = [
    	...config.transformer.assetPlugins,
    	'expo-asset/tools/hashAssetFiles',
    ];
    module.exports = config;
    Step 2: Create a New Project

    Next, we need to create a new project. Click the "New Project" button once you login into the Supabase app, and follow the wizard to create your API project.

    2a. Click "New Project"
    2b. Enter Organization Name
    2c. Enter project details
    2d. Project dashboard

    3. Create Database Table

    Now we need to create our database table to store our to-do tasks.

    Start by clicking the Table Editor button on the left sidebar menu. Once you're on this page, press the "Create a new table" button.

    Press the "Create a new table" button

    When the form opens, add the following columns to match the screenshot below:

    1. id (uuid)

    2. title (varchar)

    3. description (text)

    4. completed (bool)

    5. created_at (timestamptz)

    Make sure "Enable Row Level Security" is NOT checked.

    Add table columns

    4. Build GraphQL Schema

    Since we are going to be building our app on top of GraphQL query language, we need to tell Supabase to build its schema.

    On the left sidebar menu click the "SQL Editor" button. Once on this screen, press the "+ New Query" button.

    Now enter the following code snippet in the editor and press "RUN"

    5. API Details

    Go to Settings => API page. Here you can find the API URL and Key as shown in the screenshot below.

    That's all. We're done with creating our API for the project. All we need to do is consume it now.

    Supabase
    supabase.com
    Step 2: Create a Button to Navigate

    But we still need a way to navigate to this route in our UI. This time we will add an icon button in the header.

    Create the following component:

    Also, create an index file to export the component:

    Step 3: Add Button in Header

    To add our new TaskCreateButton to the screen, add the headerRight property in the route options like so:

    That's it, run your app to test it out.

    http://localhost:19006/p/tasks/create
    src/components/TaskCreateButton/index.ts
    export * from './TaskCreateButton';
    
    import { TaskCreateButton } from './TaskCreateButton';
    export default TaskCreateButton;
    ,
    bottom-tabs
    ,
    drawer
    ).

    Inside the navigator, we add our desired routes:

    When you run the app, you should now see 2 tabs on the main page like the screenshots below:

    Web
    iOS

    Step 2: Tab Icons

    Another thing we can do to make our UI pleasing is by adding icons to our tabs. This is done by:

    • Adding tabBarIcon to route.options.

    • Adding tabBarOptions to the navigator.

    See the code below for example:

    Web
    iOS
    CreateTaskScreen
  • EditTaskScreen

  • CompletedTasksScreen

  • So now we should have 4 screens in total.

    Step 2: Adding Edit Route

    Add the following EditTask route to the routes array:

    This should create the second route, which should be accessible via the following URL:

    Step 3: Create a Button to Navigate

    But we still need a way to navigate to this route in our UI. Let's modify our PendingTasksScreen component to add a button:

    Notice, that we use the push method from BlueBase's useNavigation hook. The method takes route name and optional params object as its arguments.

    Run your app, you should now be able to navigate between routes.

    Step 4: Extract Route Param

    Notice that the value of path property in the route is :taskId. This means that any value that is entered in this part of the URL will be captured in the taskId param. For example the URL http://localhost:19006/p/tasks/123 will extract value 123 in the taskId variable.

    Let's see how we can use this value, modify the EditTaskScreen component to match the following content:

    Notice the code on Line 7, we use the getParam function to extract the URL param.

    Loading State: When data is loading from the database. The modern way is to show a placeholder component. To achieve this, we use the @bluebase/plugin-rn-placeholder plugin. This will add the components from the rn-placeholder library in BlueBase. Make sure you install and add this plugin to your project.

  • Data State: This will render the task data after it is loaded from the database. For this, we will use List.Item component.

  • See the following code for a reference implementation:

    Step 2: Task List Empty State

    We also need to create a component that will be rendered when a list is empty. This component will show an empty message, as well as a call to action button to create a new task:

    Step 3: GraphQL Query

    Now with these views out of the way, it's time to start creating the actual list. The first step is to write our GraphQL query.

    We intend to create our list with infinite scrolling enabled. So, whenever new data is loaded, we need to merge it with the existing data, to show it as one list. To achieve this, we also create a function in this file called TasksCollectionQueryUpdateQueryFn .

    There is a bug in Supabase that doesn't let us send "last" and "before" params. When it is fixed, these lines should be uncommented.

    Step 4: Generate Typings

    We will now generate typescript interfaces for our GraphQL API. Execute the following command:

    This will update the file src/graphql-types.ts.

    Step 5: Task List

    With all the prep work now complete, let's finally create our list. For this we will use the GraphqlList component from the @bluebase/plugin-json-graphql-components plugin. This component takes care of all the heavy lifting.

    We create this component with a complete prop so that we can filter task based on this value. This will be passed as a variable to the GraphQL query.

    Create the index file:

    Step 6: Update screens

    Last but not the least, lets add this list to the PendingTasksScreen and CompletedTasksScreen screens:

    Refresh the app, and see it come to life. Enjoy!

    Loading State

    Loading State on Web
    Loading State on iOS

    Empty State

    Empty State on Web
    Empty State on iOS

    Data State

    Data State on Web
    Data State on iOS
    -- Rebuild the GraphQL Schema Cache
    select graphql.rebuild_schema();
    src/routes.ts
    export const routes = [
    {
    	name: 'CreateTask',
    	screen: 'CreateTaskScreen',
    	path: 'create',
    	exact: true,
    
    	options: {
    		title: 'Create Task',
    	},
    },
    {
    	name: 'EditTask',
    	screen: 'EditTaskScreen',
    	path: 't/:taskId',
    	exact: true,
    
    	options: {
    		title: 'Edit Task',
    	},
    },
    {
    	name: 'TasksApp',
    	screen: 'PendingTasksScreen',
    	path: '',
    	exact: false,
    
    	options: {
    		title: 'My Tasks',
    	},
    }];
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    
    import { ToDoAppIcon } from './components/ToDoAppIcon';
    import { routes } from './routes';
    import { screens } from './screens';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	components: {
    		// Components
    		ToDoAppIcon,
    
    		// Screens
    		...screens,
    	},
    
    	icon: {
    		component: 'ToDoAppIcon',
    		type: 'component',
    	},
    
    	indexRoute: 'TasksApp',
    
    	routes,
    });
    
    src/components/TaskCreateButton/TaskCreateButton.tsx
    import { IconButton } from '@bluebase/components';
    import { useNavigation, useTheme } from '@bluebase/core';
    import React, { useCallback } from 'react';
    
    export interface TaskCreateButtonProps {}
    
    export const TaskCreateButton = (_props: TaskCreateButtonProps) => {
    	const { theme } = useTheme();
    	const { navigate } = useNavigation();
    
    	const onPress = useCallback(() => {
    		navigate('CreateTask');
    	}, []);
    
    	return (
    		<IconButton
    			name="plus"
    			onPress={onPress}
    			color={theme.palette.text.secondary}
    		/>
    	);
    };
    src/routes.ts
    {
    	name: 'TasksApp',
    	screen: 'PendingTasksScreen',
    	path: '',
    	exact: false,
    
    	options: {
    		title: 'My Tasks',
    		headerRight: () => <TaskCreateButton />
    	},
    }
    {
    	name: 'TasksApp',
    	screen: Noop,
    	path: '',
    	exact: false,
    	
    	options: {
    		title: 'My Tasks',
    		headerRight: () => <TaskCreateButton />
    	},
    	
    	navigator: {
    		headerMode: 'none',
    		type: 'tab',
    	
    		routes: [{
    			exact: true,
    			name: 'PendingTask',
    			path: 'pending',
    			screen: 'PendingTasksScreen',
    
    			options: {
    				title: 'Pending',
    			},
    		}, {
    			exact: true,
    			name: 'CompletedTasks',
    			path: 'completed',
    			screen: 'CompletedTasksScreen',
    
    			options: {
    				title: 'Completed',
    			},
    		}],
    	},
    }
    src/routes.ts
    {
    	name: 'TasksApp',
    	screen: Noop,
    	path: '',
    	exact: false,
    	
    	options: {
    		title: 'My Tasks',
    		headerRight: () => <TaskCreateButton />
    	},
    	
    	navigator: {
    		headerMode: 'none',
    		type: 'tab',
    	
    		routes: [{
    			exact: true,
    			name: 'PendingTask',
    			path: 'pending',
    			screen: 'PendingTasksScreen',
    
    			options: {
    				title: 'Pending',
    				tabBarIcon: ({ color }) => <Icon name="checkbox-multiple-blank-outline" color={color} />,
    			},
    		}, {
    			exact: true,
    			name: 'CompletedTasks',
    			path: 'completed',
    			screen: 'CompletedTasksScreen',
    
    			options: {
    				title: 'Completed',
    				tabBarIcon: ({ color }) => <Icon name="checkbox-multiple-marked" color={color} />,
    			},
    		}],
    	
    		tabBarOptions: {
    			showIcon: true,
    			tabStyle: {
    				flexDirection: 'row',
    			},
    		},
    	},
    }
    src/index.ts
    routes: [{
    	name: 'EditTask',
    	screen: 'EditTaskScreen',
    	path: 't/:taskId',
    	exact: true,
    
    	options: {
    		title: 'Edit Task',
    	},
    },
    {
    	name: 'TasksApp',
    	screen: 'PendingTasksScreen',
    	path: '',
    	exact: false,
    
    	options: {
    		title: 'My Tasks',
    	},
    }]
    http://localhost:19006/p/tasks/t/123
    import { Button, View } from '@bluebase/components';
    import { useNavigation } from '@bluebase/core';
    import React, { useCallback } from 'react';
    import { Text } from 'react-native';
    
    export const PendingTasksScreen = () => {
    	const { push } = useNavigation();
    
    	const goToEdit = useCallback(() => {
    		push('EditTask', { taskId: '123' });
    	}, []);
    
    	return (
    		<View>
    			<Text>PendingTasksScreen</Text>
    			<Button title="Go To Edit Screen" onPress={goToEdit} />
    		</View>
    	);
    };
    
    PendingTasksScreen.displayName = 'PendingTasksScreen';
    src/screens/EditTaskScreen/EditTaskScreen.tsx
    import { useNavigation } from '@bluebase/core';
    import React from 'react';
    import { Text, View } from 'react-native';
    
    export const EditTaskScreen = () => {
    	const { getParam } = useNavigation();
    	const taskId = getParam('taskId', null);
    
    	return (
    		<View style={{ padding: 10 }}>
    			<Text>EditTaskScreen: {taskId}</Text>
    		</View>
    	);
    };
    
    EditTaskScreen.displayName = 'EditTaskScreen';
    src/components/TaskListItem/TaskListItem.tsx
    import { Checkbox, List } from '@bluebase/components';
    import { getComponent, useNavigation } from '@bluebase/core';
    import { PlaceholderListItemProps } from '@bluebase/plugin-rn-placeholder';
    import React, { useCallback } from 'react';
    
    import { Tasks } from '../../graphql-types';
    
    const PlaceholderListItem = getComponent<PlaceholderListItemProps>('PlaceholderListItem');
    
    export interface TaskListItemProps extends Tasks {
    	loading?: boolean;
    }
    
    export const TaskListItem = (props: TaskListItemProps) => {
    	const {
    		id,
    		title,
    		description,
    		completed,
    		loading,
    	} = props;
    
    	if (loading === true) {
    		return <PlaceholderListItem avatar description variant="icon" />;
    	}
    
    	const { push } = useNavigation();
    
    	const onPress = useCallback(() => {
    		push('EditTask', { taskId: id });
    	}, [id]);
    
    	return (
    		<List.Item
    			title={title}
    			description={description}
    			left={<Checkbox checked={!!completed} disabled />}
    			onPress={onPress}
    		/>
    	);
    };
    
    TaskListItem.defaultProps = {};
    TaskListItem.displayName = 'TaskListItem';
    src/components/TaskListItem/index.ts
    export * from './TaskListItem';
    
    import { TaskListItem } from './TaskListItem';
    export default TaskListItem;
    src/components/TaskListEmptyState/TaskListEmptyState.tsx
    import { ComponentState, ComponentStateProps } from '@bluebase/components';
    import { useNavigation } from '@bluebase/core';
    import React, { useCallback } from 'react';
    
    export interface TaskListEmptyStateProps extends ComponentStateProps {}
    
    export const TaskListEmptyState = (props: TaskListEmptyStateProps) => {
    	const { navigate } = useNavigation();
    
    	const goToCreate = useCallback(() => navigate('CreateTask'), []);
    
    	return (
    		<ComponentState
    			title="No tasks"
    			description="Start by creating a new task"
    			actionTitle="Create Task"
    			imageProps={{ resizeMode: 'contain' }}
    			actionOnPress={goToCreate}
    			actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
    			{...props}
    		/>
    	);
    };
    
    TaskListEmptyState.displayName = 'TaskListEmptyState';
    src/components/TaskListEmptyState/index.ts
    export * from './TaskListEmptyState';
    
    import { TaskListEmptyState } from './TaskListEmptyState';
    export default TaskListEmptyState;
    src/components/TaskList/TasksCollectionQuery.graphql.ts
    import { FetchMoreOptions } from '@apollo/client';
    import gql from 'graphql-tag';
    
    import { TasksCollectionQueryQuery } from '../../graphql-types';
    
    export const TasksCollectionQuery = gql`
    	query TasksCollectionQuery(
    		$filter: tasksFilter
    		$first: Int
    		# $last: Int
    		# $before: Cursor
    		$after: Cursor
    	) {
    		tasksCollection(
    			filter: $filter
    			first: $first
    			# last: $last
    			# before: $before
    			after: $after
    		) {
    			edges {
    	      cursor
    				node {
    					id
    					title
    					completed
    				}
    			}
    			pageInfo {
    				endCursor
    				hasNextPage
    				hasPreviousPage
    				startCursor
    			}
    		}
    	}
    `;
    
    export const TasksCollectionQueryUpdateQueryFn: FetchMoreOptions<TasksCollectionQueryQuery>['updateQuery'] = (
    	previousResult,
    	{ fetchMoreResult }
    ) => {
    	if (!fetchMoreResult) {
    		return previousResult;
    	}
    
    	const prevEdges = previousResult.tasksCollection?.edges || [];
    	const newEdges = fetchMoreResult.tasksCollection?.edges || [];
    
    	return {
    		// Put the new items at the end of the list and update `pageInfo`
    		// so we have the new `endCursor` and `hasNextPage` values
    		tasksCollection: {
    			...previousResult.tasksCollection,
    
    			edges: [...prevEdges, ...newEdges],
    
    			pageInfo: {
    				...previousResult.tasksCollection?.pageInfo,
    
    				endCursor: fetchMoreResult.tasksCollection?.pageInfo?.endCursor,
    				hasNextPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasNextPage,
    				hasPreviousPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasPreviousPage,
    				startCursor: fetchMoreResult.tasksCollection?.pageInfo?.startCursor,
    			},
    		},
    	};
    };
    yarn graphql-codegen
    src/components/TaskList/TaskList.tsx
    import { QueryResult } from '@apollo/client';
    import { Divider } from '@bluebase/components';
    import { getComponent } from '@bluebase/core';
    import { GraphqlConnection, GraphqlListProps } from '@bluebase/plugin-json-graphql-components';
    import React from 'react';
    import { ListRenderItemInfo } from 'react-native';
    
    import { Tasks, TasksCollectionQueryQuery } from '../../graphql-types';
    import TaskListEmptyState from '../TaskListEmptyState';
    import { TaskListItem, TaskListItemProps } from '../TaskListItem';
    import { TasksCollectionQuery, TasksCollectionQueryUpdateQueryFn } from './TasksCollectionQuery.graphql';
    
    const GraphqlList = getComponent<GraphqlListProps<TaskListItemProps, TasksCollectionQueryQuery>>('GraphqlList');
    
    function mapQueryResultToConnection(result: QueryResult<TasksCollectionQueryQuery>) {
    	return result.data?.tasksCollection as GraphqlConnection<Tasks>;
    }
    
    function renderItem({ item }: ListRenderItemInfo<TaskListItemProps>) {
    	return <TaskListItem {...item} />;
    }
    
    const renderDivider = () => <Divider inset />;
    
    export interface TaskListProps {
    	completed: boolean;
    }
    
    export const TaskList = (props: TaskListProps) => {
    	const { completed } = props;
    	const itemsPerPage = 10;
    
    	return (
    		<GraphqlList
    			key="task-list"
    			pagination="infinite"
    			itemsPerPage={itemsPerPage}
    			query={TasksCollectionQuery}
    			updateQueryInfinitePagination={TasksCollectionQueryUpdateQueryFn}
    			mapQueryResultToConnection={mapQueryResultToConnection}
    			renderItem={renderItem}
    			ItemSeparatorComponent={renderDivider}
    			ListEmptyComponent={TaskListEmptyState}
    			queryOptions={{
    				variables: {
    					filter:{ completed: { eq: completed } }
    				}
    			}}
    		/>
    	);
    };
    
    TaskList.displayName = 'TaskList';
    src/components/TaskList/index.ts
    export * from './TaskList';
    
    import { TaskList } from './TaskList';
    export default TaskList;
    src/screens/PendingTasksScreen/PendingTasksScreen.tsx
    import React from 'react';
    
    import { TaskList } from '../../components/TaskList';
    
    export const PendingTasksScreen = () => {
    	return (
    		<TaskList completed={false} />
    	);
    };
    
    PendingTasksScreen.displayName = 'PendingTasksScreen';
    src/screens/CompletedTasksScreen/CompletedTasksScreen.tsx
    import React from 'react';
    
    import TaskList from '../../components/TaskList';
    
    export const CompletedTasksScreen = () => {
    	return (
    		<TaskList completed />
    	);
    };
    
    CompletedTasksScreen.displayName = 'CompletedTasksScreen';

    5. Enhancements

    4.3 Updating Tasks

    Our users may want to edit their tasks or mark them as complete. We will have to create a form very similar to the one in Chapter 4.1. The only difference is that we need to query task data and prefill the form with it.

    Step 1: Create Mutation

    Let's start by creating an update mutation. This will be used when a user submits the form.

    Step 2: Generate Typings

    We will now generate typescript interfaces for our GraphQL API. Execute the following command:

    This will update the file src/graphql-types.ts.

    Step 3: Create Form

    Create the following component:

    Explanation:

    • Line 22: We take task ID as a prop. This will be used as a query variable.

    • Line 28: A function that takes the query result as input (array of tasks) and returns a single task. This task is used as the initial value of the form.

    • Line 33: A function that takes form data and converts it into mutation variables. This function is called when a user submits the form.

    Step 4: Add Form to Screen

    Let's add our form to the EditTaskScreen component:

    When you run the app, you should results similar to the screenshot below:

    5.3 Dynamic Images

    Let's make our app a bit visually pleasing by adding some images. The perfect place to do this is in the TaskListEmptyState component.

    Step 1: Download Image

    Download the following image and put it at ssets/no-tasks-light.png.

    Step 2: Add the Image to the Plugin

    Modify your plugin to add an assets property like the in the code below:

    Basically, we're telling BlueBase that the said image has an ID NoTasks and where to find it. Now we can just reference this image by this ID and BlueBase will take care of loading and rendering it across formats.

    Step 3: Use the Image in the Component

    Modify the ComponentState node in the TaskListEmptyState component and add a prop imageSource="NoTasks" as shown in the code below:

    Now when you refresh the app, you should see an image in the empty state.

    Step 4: Use a Different Image for Dark Mode

    Sometimes we need a different variant of an image for dark mode. Don't worry, we have got you covered here as well.

    Download the following image and put it at: assets/no-tasks-dark.png.

    Now, modify the assets property in the plugin to match the following:

    Try refreshing your app. You'll see different images will be rendered in light mode and dark mode.

    Basically, you can define a different image with the same ID (NoTasks in this example) for all of the following scenarios:

    Key
    Use Case

    5.2 Theming

    BlueBase was built from the ground up with theming in mind. It is an extensive topic and we encourage you to read the Themes section of the docs.

    Just to see a bit of a demo, we will customise the styles of our custom component from app configurations.

    You remember creating the ToDoAppIcon component in Chapter 1.3:

    Explanation:

    • Line 2: We import 2 hooks from BlueBase core: useStyles and useTheme.

    • Line 6: We define the interface of the stylesheet for the component.

    • Line 13: Add the styles property as an optional prop.

    • Line 18: We access the currently active theme from the useTheme hook.

    • Line 20: Extract the final styles from the useStyles hook. This hook takes 3 params:

      1. Name of the component. We can use this name in our themes to override the styles of this component.

      2. Input props of the component. These may contain the styles prop, as well as any other prop that is needed. Note that these props are also passed on to the defaultStyles

    • Line 35 & 39: We use the styles in our component.

    Customize Component Styles

    Let's see how we can customize the appearance of this component from our app configs.

    Create a prop in the configs by key theme.overrides. The value of this property is a theme object. Here you can override a theme's styles.

    In the example below, we are basically telling BlueBase to override the styles of the ToDoAppIcon component in light mode.

    Note that the value of configs['theme.overrides'].light.components.ToDoAppIcon is an object that matches the ToDoAppIconStyles interface.

    Now when you run your app, you will notice the app icon color is purple in light mode, and not red.

    4.4 Deleting Tasks

    Lastly, we want to allow our users to be able to delete tasks.

    Step 1: Write Mutation

    Let's write a mutation to delete our task.

    src/components/TaskDeleteButton/DeleteTaskMutation.graphq l.ts
    import gql from 'graphql-tag';
    
    export const DeleteTaskMutation = gql`
    	mutation DeleteTaskMutation($id: UUID!) {
    		deleteFromtasksCollection(filter: { id: { eq: $id } }) {
    			records {
    				id
    			}
    		}
    	}
    `;

    Step 2: Generate Typings

    We will now generate typescript interfaces for our GraphQL API. Execute the following command:

    This will update the file src/graphql-types.ts.

    Step 3: Create Delete Button

    Now lets an IconButton to delete the task.

    Step 4: Add Button to EditTaskScreen

    Modify the EditTaskScreen to add the headerRight option:

    This should be the result:

    5.1 Internationalisation

    Internationalisation is a big part of modern apps. BlueBase has built-in support for translations and right-to-left (RTL) text support.

    Go you the Settings app, and select a language. If the language needs RTL, BlueBase will change orientation automatically.

    Changing content direction on native requires app to be restarted.

    iOS
    Web

    You can add support for any language you desire. Here are the steps on how to do this:

    Step 1: Adding Language to the system

    We support English and Urdu languages by default. If you need to add support for any other language just pass them to the locale.options object in your configs file, where the key of the object is the language code, and value is the language name.

    Step 2: Provide Translations

    Next, we have to create a dictionary, that the system can refer to when attempting to translate a string. The dictionary is an object where where original string is the key, and the translated version is the value.

    The way to do it is, we create a function that takes a dictionary as input, and we return that dictionary by appending our own translations to it. This is because the translations feature in BlueBase is built upon its feature.

    The filter key for format to add translations is: bluebase.intl.messages.${language_code}. So for french language it would be bluebase.intl.messages.fr.

    When we have all our language filters setup, we just need to add them to the filters object in the plugin.

    Step 3: Translating Strings

    While we attempt to give built in conversion support for our official plugins, you may still need to do one additional step to convert your string to the current locale.

    We'll take example of our TaskListEmptyState component. When you select a language different than English, you will note that the text is still not translated. Let's change that.

    Go to your component and change to code to the following:

    You will note that:

    • Line 2: We import useIntl hook from @bluebase/core.

    • Line 9: We extract the __ function from the useIntl() hook.

    That's all! Since we have already provided translations to these strings in previous step, when you refresh the app you will now observe the text to be converted.

    5.4 Settings & Configurations

    Now that we have created our plugin that adds the Tasks functionality to our app, let's see if we can make it configurable.

    There are "configurable", we mean 2 things:

    1. A developer that is using this plugin, can configure it via the configs prop.

    2. An end-user can configure the feature through the "Settings" UI.

    We will attempt to do both these things in this chapter. For this example, we will make the number of items our Task List loads per page.

    Step 1.1: Consume a Variable from the Config

    Let's go back to our TaskList component from .

    Change Line 32 from:

    To:

    Basically, rather than having a hardcoded number in the code, we extract it from config with key 'tasks.itemsPerPage'. We utilize the useConfig hook for this purpose, that is imported from the @bluebase/core package.

    Note that the useConfig hook has the same API as the useState hook in the react library.

    This should be your final code now:

    Step 1.2 Add Default Configs

    Now that we are using the config, we also need to define its default value. Add the defaultConfigs object to your plugin as shown in the code below:

    Now run your app and inspect. You will observe that BlueBase has successfully loaded the default config, which has been passed onto the GraphqlList component.

    Step 1.3: Override Config Value

    Now comes the fun part. Let's override this value from our app's configs:

    Run your app again. You will observe that the value in the configs was loaded and preferred over the defaultConfigs.

    Now, let's see how we can allow the end-user to customize the value too.

    Step 2.1: Create a Form

    Let's create a form that will allow the user to input a value, and update the config when he presses the submit button.

    For this purpose, we will use the JsonForm component from the @bluebase/plugin-json-schema-components.

    The props of JsonForm component are very similar to the GraphqlJsonForm that we have previously usesd.

    This is because the GraphqlJsonForm too uses JsonForm internally.

    JsonForm component is built using the library.

    Add the index file to export the component.

    Step 2.2 Add Screen in the Settings App

    Now we want to create a screen for "Tasks" in the Settings app. Luckily the Settings app allows this to be done via bluebase.plugin.setting-app.pages filter.

    This filter inputs an array of screens in the settings app, all we have to do is to append our own screen configs and return the array. Create a new file and copy the following code:

    Note that in Line 22 till 27 we define the "Task Settings" panel and use the TaskSettingsForm component that we created in the previous step.

    Let's add this filter to the plugin (See Line 13):

    Run the app and go to the settings section. You will see the "Tasks" settings page is added.

    When you use and submit the form, you will observe that it will update the config to achieve the desired result:

    4.1 Creating Tasks

    The first thing to do would be to create a form to insert a new task in the database.

    BlueBase provides a utility to create forms quickly by just writing a simple JSON-based schema. Let's get right to it.

    Step 1: Install the plugin

    Add the following plugin as a dependency to the project:

    Then import and use this plugin to BlueBase:

    Step 2: Create Mutation

    Now we're going to write a GraphQL mutation to insert a new task in the database. Create the following file.

    This tutorial does not cover how GraphQL works. If you are new to it, head over to this .

    Step 3: Generate Typings

    We will now generate typescript interfaces for our GraphQL API. Execute the following command:

    This will create a new file at: src/graphql-types.ts.

    Step 4: Create Form

    Create the follwing files:

    Here's what's happening in the file above:

    • Line 8: We import the JsonGraphqlForm from the BlueBase context. This component was added to our app by the @bluebase/plugin-json-graphql-components plugin.

    • Line 15: We create a callback function, that will be called once the form is successfully submitted to the API. Here we want to navigate to the main page after success.

    • Line 19: If the output data of the form is different from what the API expects, we can define a function to map it to the right format. In this case, the form inputs a single task object, but the API expects an array. So we do the conversion here. This function is passed as a prop to the

    Finally, we create the index file to export the component:

    Step 5: Use Form on Screen

    Now that we have created our form, it's time to add it to our layout. Change the the CreateTaskScreen component match the following code:

    We're all done. Time to take our form for a test drive. Input some data into the form and submit it.

    If all goes well, you should now be redirected to the task list screen. Go to Supabase admin panel, and then to the table editor to check if the data was in fact inserted into the database.

    Plugin API

    A plugin is just a JavaScript object that has the following properties:

    Key

    Type

    Required

    Description

    name

    string

    yes

    Name of the plugin.

    key

    string

    Plugin Categories

    Categories are used in plugin listing pages. Currently, we recognise the following kind of plugins, but a developer may define his own category as well.

    Example

    src/components/EditTaskForm/UpdateTaskMutation.graphql.ts
    import gql from 'graphql-tag';
    
    export const UpdateTaskMutation = gql`
    	mutation UpdateTaskMutation(
    		$id: UUID!
    		$title: String
    		$description: String
    		$completed: Boolean
    	) {
    		updatetasksCollection(
    			filter: { id: { eq: $id } }
    			set: {
    				title: $title,
    				description: $description,
    				completed: $completed
    			}
    		) {
    			affectedCount
    			records {
    				id
    				title
    				description
    				completed
    			}
    		}
    	}
    `;
    
    src/components/ToDoAppIcon/ToDoAppIcon.tsx
    import { DynamicIcon, View } from '@bluebase/components';
    import { useStyles, useTheme } from '@bluebase/core';
    import React from 'react';
    import { TextStyle, ViewStyle } from 'react-native';
    
    export interface ToDoAppIconStyles {
    	iconColor: { color: TextStyle['color'] };
    	root: ViewStyle;
    }
    
    export interface ToDoAppIconProps {
    	size: number;
    	styles?: Partial<ToDoAppIconStyles>;
    }
    
    export const ToDoAppIcon = (props: ToDoAppIconProps) => {
    	const { size } = props;
    	const { theme } = useTheme();
    
    	const styles: ToDoAppIconStyles = useStyles('ToDoAppIcon', props, {
    		iconColor: {
    			color: theme.palette.error.contrastText,
    		},
    		root: {
    			backgroundColor: theme.palette.error.main,
    			borderRadius: theme.shape.borderRadius * 3,
    			alignItems: 'center',
    			justifyContent: 'center',
    			height: size,
    			width: size,
    		},
    	});
    
    	return (
    		<View style={styles.root}>
    			<DynamicIcon
    				type="icon"
    				name="checkbox-multiple-marked-outline"
    				color={styles.iconColor.color}
    				size={size * 0.75}
    			/>
    		</View>
    	);
    };
    
    ToDoAppIcon.defaultProps = {
    	size: 100,
    };
    
    yarn add @bluebase/plugin-json-graphql-components
    plugins.ts
    import BlueBasePluginApollo from '@bluebase/plugin-apollo';
    import BlueBasePluginJsonGraphqlComponents from '@bluebase/plugin-json-graphql-components';
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginMaterialUI from '@bluebase/plugin-material-ui';
    import BlueBasePluginReactRouter from '@bluebase/plugin-react-router';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginApollo,
    	BlueBasePluginJsonGraphqlComponents,
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginMaterialUI,
    	BlueBasePluginReactRouter,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    
    plugins.native.ts
    import BlueBasePluginApollo from '@bluebase/plugin-apollo';
    import BlueBasePluginJsonGraphqlComponents from '@bluebase/plugin-json-graphql-components';
    import BlueBasePluginJsonSchemaComponents from '@bluebase/plugin-json-schema-components';
    import BlueBasePluginLauncher from '@bluebase/plugin-launcher';
    import BlueBasePluginReactNativePaper from '@bluebase/plugin-react-native-paper';
    import BlueBasePluginReactNavigation from '@bluebase/plugin-react-navigation';
    import BlueBasePluginResponsiveGrid from '@bluebase/plugin-responsive-grid';
    import BlueBasePluginSettingsApp from '@bluebase/plugin-settings-app';
    import { MaterialCommunityIcons } from '@bluebase/plugin-vector-icons';
    
    import Plugin from './src';
    
    export const plugins = [
    	BlueBasePluginApollo,
    	BlueBasePluginJsonGraphqlComponents,
    	BlueBasePluginJsonSchemaComponents,
    	BlueBasePluginLauncher,
    	BlueBasePluginReactNativePaper,
    	BlueBasePluginReactNavigation,
    	BlueBasePluginResponsiveGrid,
    	MaterialCommunityIcons,
    
    	Plugin,
    	BlueBasePluginSettingsApp,
    ];
    

    User Management

    Components

    React as a UI library puts a lot of emphasis on components. BlueBase stores components which can be dynamically used and replaced. This plays a major role in creating a pluggable and modular system.

    Line 57: We tell the GraphQL client to fetch data for our list query when the mutation is successful. If we don't do this, even after we successfully update a task, going back to the list screen will show old data. This is because Apollo will show data from its local cache. By retching a query we update the local cache.
    Web
    iOS

    Desktop Screen Size version on Dark mode

    NoTasks_mobile_dark

    Mobile Screen Size version on Dark mode

    NoTasks_desktop_light

    Desktop Screen Size version on Light mode

    NoTasks_mobile_light

    Mobile Screen Size version on Light mode

    NoTasks

    Default version

    NoTasks_dark

    Dark mode version

    NoTasks_light

    Light mode verion

    NoTasks_desktop

    Desktop Screen Size version

    NoTasks_mobile

    Mobile Screen Size version

    Light Mode
    Light Mode
    Dark Mode
    Dark Mode

    NoTasks_desktop_dark

    prop if it's a function.
  • The default styles. These are the base styles that may be overwritten by theme overrides. This param may be an object (of the interface defined at Line 6), or a function that returns this object.

  • Without Customization
    With Customization
    Web
    iOS
    Line 15-17
    : We use the
    __
    function to translate strings.
    Filters
    iOS
    Web
    iOS
    Web
    Chapter 4.2
    formik
    JsonGraphqlForm
    component.
  • Line 26: We pass our mutation to the form. This will be called when the form is submitted.

  • Line 31: This is our JSON schema that generates the form. Check the plugin docs for more information on its API.

  • official tutorial
    Web
    iOS

    A plugin that provides integration with a logging service. i.e. Google Analytics.

    no

    This property is used as an ID throughout the system.

    description

    string

    no

    Plugin description.

    version

    string

    no

    Plugin version.

    categories

    string[]

    no

    Plugin Categories.

    icon

    DynamicIconProps

    no

    These are props of the DynamicIcon component. This value can also be a thunk, i.e. a function that should return the props object. This function receives BlueBase context as param.

    enabled

    boolean

    no

    (Default = true) Flag to check if a plugin is enabled.

    filters

    FilterNestedCollection

    no

    components

    ComponentCollection

    no

    themes

    ThemeCollection

    no

    defaultConfigs

    ConfigCollection

    no

    Key

    Description

    app

    A plugin that is a sub-app in the system. This applies that when BlueBase is used as a bigger platform, and different features are represented as individuals apps inside the platform.

    store

    A plugin that provides a state store functionality. i.e. Redux.

    router

    A plugin that provides routing mechanism. i.e. react-router or React Navigation.

    logging

    A plugin that provides integration with a logging service. i.e. Sentry.

    theme

    A plugin that provides a single or multiple themes.

    analytics

    Developing an Analytics Plugin

    You can add support for any analytics service provider by using the bluebase.analytics.track filter.

    import { createPlugin } from '@bluebase/core';
    ​
    export const AnalyticsPlugin = createPlugin({
    ​
        key: 'analytics',
        name: 'Analytics Plugin',
        categories: ['analytics'],
    
        filters: {
            'bluebase.analytics.track': (data: AnalyticsTrackData) => {
                // send data to analytics provider here
            }
        }
    });
    yarn graphql-codegen
    src/components/EditTaskForm/EditTaskForm.tsx
    import { getComponent, useNavigation } from '@bluebase/core';
    import { JsonGraphqlFormProps } from '@bluebase/plugin-json-graphql-components';
    import React, { useCallback } from 'react';
    
    import {
    	TasksCollectionQueryQuery,
    	TasksCollectionQueryQueryVariables,
    	TasksInsertInput,
    	UpdateTaskMutationMutationVariables
    } from '../../graphql-types';
    import { TasksCollectionQuery } from '../TaskList/TasksCollectionQuery.graphql';
    import { UpdateTaskMutation } from './UpdateTaskMutation.graphql';
    
    const JsonGraphqlForm = getComponent<JsonGraphqlFormProps<any>>('JsonGraphqlForm');
    
    export interface EditTaskFormProps {
    	id: string;
    }
    
    export const EditTaskForm = (props: EditTaskFormProps) => {
    	const { navigate } = useNavigation();
    	const { id } = props;
    
    	const onSuccess = useCallback(() => {
    		navigate('TasksApp');
    	}, []);
    
    	const mapQueryDataToInitialValues = useCallback(
    		(data: TasksCollectionQueryQuery) => {
    			return data?.tasksCollection?.edges[0]?.node;
    		}, []);
    
    	const mapFormValuesToMutationVariables = useCallback(
    		(task: TasksInsertInput): UpdateTaskMutationMutationVariables => {
    			return {
    				id: task.id,
    				title: task.title,
    				description: task.description,
    				completed: task.completed
    			};
    		}, []);
    
    	const queryVariables: TasksCollectionQueryQueryVariables = {
    		filter: {
    			id: { 'eq': id }
    		}
    	};
    
    	return (
    		<JsonGraphqlForm
    			query={{
    				query: TasksCollectionQuery,
    				variables: queryVariables
    			}}
    			mutation={{
    				mutation: UpdateTaskMutation,
    				refetchQueries: [TasksCollectionQuery],
    				awaitRefetchQueries: true
    			}}
    			onSuccess={onSuccess}
    			mapFormValuesToMutationVariables={mapFormValuesToMutationVariables}
    			mapQueryDataToInitialValues={mapQueryDataToInitialValues}
    			{...props}
    			schema={{
    				validateOnBlur: false,
    				validateOnChange: false,
    
    				fields: [
    					{
    						autoFocus: true,
    						label: 'Title',
    						name: 'title',
    						required: true,
    						type: 'text',
    					},
    					{
    						label: 'Description',
    						name: 'description',
    						type: 'text',
    					},
    					{
    						label: 'Completed',
    						name: 'completed',
    						type: 'checkbox',
    					},
    					{
    						name: 'status',
    						type: 'status',
    					},
    					{
    						name: 'submit',
    						title: 'Update Task',
    						type: 'submit',
    					},
    				],
    			}}
    		/>
    	);
    };
    
    EditTaskForm.displayName = 'EditTaskForm';
    src/components/EditTaskForm/index.ts
    export * from './EditTaskForm';
    
    import { EditTaskForm } from './EditTaskForm';
    export default EditTaskForm;
    src/screens/EditTaskScreen/EditTaskScreen.tsx
    import { useNavigation } from '@bluebase/core';
    import React from 'react';
    
    import EditTaskForm from '../../components/EditTaskForm';
    
    export const EditTaskScreen = () => {
    	const { getParam } = useNavigation();
    	const taskId = getParam('taskId', null);
    
    	return (
    		<EditTaskForm id={taskId} />
    	);
    };
    
    EditTaskScreen.displayName = 'EditTaskScreen';
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    
    import { ToDoAppIcon } from './components/ToDoAppIcon';
    import { filters } from './filters';
    import { lang } from './lang';
    import { routes } from './routes';
    import { screens } from './screens';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	assets: {
    		NoTasks: require('../assets/no-tasks-light.png'),
    	},
    
    	// ... Other properties
    });
    src/components/TaskListEmptyState/TaskListEmptyState.tsx
    <ComponentState
        imageSource="NoTasks"
        title={__('No tasks')}
        imageProps={{ resizeMode: 'contain' }}
        description={__('Start by creating a new task')}
        actionTitle={__('Create Task')}
        actionOnPress={goToCreate}
        actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
        {...props}
    />
    assets: {
        NoTasks_dark: require('../assets/no-tasks-dark.png'),
        NoTasks_light: require('../assets/no-tasks-light.png'),
    },
    export const configs = {
    
    	// ... Other Configs
    
    	'theme.overrides': {
    		light: {
    			components: {
    				ToDoAppIcon: {
    					root: {
    						backgroundColor: '#6750A4',
    					}
    				}
    			}
    		}
    	}
    };
    
    yarn graphql-codegen
    src/components/TaskDeleteButton/TaskDeleteButton.tsx
    import { useMutation } from '@apollo/client';
    import { IconButton } from '@bluebase/components';
    import { useNavigation, useTheme } from '@bluebase/core';
    import React, { useCallback } from 'react';
    
    import { DeleteTaskMutationMutation } from '../../graphql-types';
    import { TasksCollectionQuery } from '../TaskList/TasksCollectionQuery.graphql';
    import { DeleteTaskMutation } from './DeleteTaskMutation.graphql';
    
    export interface TaskDeleteButtonProps {
    	id: string
    }
    
    export const TaskDeleteButton = (props: TaskDeleteButtonProps) => {
    	const { id } = props;
    	const { theme } = useTheme();
    	const { navigate } = useNavigation();
    
    	const onCompleted = useCallback(() => {
    		navigate('TasksApp');
    	}, []);
    
    	const [deleteTask, { loading }] = useMutation<DeleteTaskMutationMutation>(DeleteTaskMutation, {
    		onCompleted,
    		refetchQueries: [TasksCollectionQuery],
    		awaitRefetchQueries: true,
    		variables: { id }
    	});
    
    	return (
    		<IconButton
    			name="delete"
    			onPress={deleteTask}
    			color={theme.palette.text.secondary}
    			disabled={loading}
    		/>
    	);
    };
    
    TaskDeleteButton.displayName = 'TaskDeleteButton';
    src/components/TaskDeleteButton/index.ts
    export * from './TaskDeleteButton';
    
    import { TaskDeleteButton } from './TaskDeleteButton';
    export default TaskDeleteButton;
    {
    	name: 'EditTask',
    	screen: 'EditTaskScreen',
    	path: 't/:taskId',
    	exact: true,
    	
    	options: ({ route }: any) => ({
    		title: 'Edit Task',
    		headerRight: () => <TaskDeleteButton id={route.params.taskId} />
    	}),
    },
    configs.ts
    export const configs = {
    
    	'locale.options': {
    		en: 'English',
    		fr: 'French',
    		ur: 'اُردُو',
    	},
    
    	// ... Other configs
    };
    src/lang/ur.ts
    import { IntlMessages } from '@bluebase/core';
    
    export const ur = (messages: IntlMessages) => ({
    	...messages,
    
    	'Title': 'عنوان',
    	'Description': 'تفصیل',
    	'Pending': 'زیر التواء',
    	'Completed': 'مکمل',
    
    	'Tasks': 'کام',
    	'My Tasks': 'میری ٹاسکس',
    	'No tasks': 'کوئی کام نہیں',
    	'Start by creating a new task': 'ایک نیا کام بنا کر شروع کریں۔',
    	'Create Task': 'ٹاسک بنائیں',
    	'Edit Task': 'ٹاسک ترمیم کریں',
    	'Update Task': 'ٹاسک تدوین کریں',
    });
    
    export default ur;
    src/lang/index.ts
    import { ur } from './ur';
    
    export const lang = {
    	'bluebase.intl.messages.ur': ur,
    };
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    
    import { ToDoAppIcon } from './components/ToDoAppIcon';
    import { lang } from './lang';
    import { routes } from './routes';
    import { screens } from './screens';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	filters: {
    		...lang,
    	},
    	
    	// ... other plugin properties
    });
    
    src/components/TaskListEmptyState/TaskListEmptyState.tsx
    import { ComponentState, ComponentStateProps } from '@bluebase/components';
    import { useIntl, useNavigation } from '@bluebase/core';
    import React, { useCallback } from 'react';
    
    export interface TaskListEmptyStateProps extends ComponentStateProps {}
    
    export const TaskListEmptyState = (props: TaskListEmptyStateProps) => {
    	const { __ } = useIntl();
    	const { navigate } = useNavigation();
    
    	const goToCreate = useCallback(() => navigate('CreateTask'), []);
    
    	return (
    		<ComponentState
    			title={__('No tasks')}
    			description={__('Start by creating a new task')}
    			actionTitle={__('Create Task')}
    			imageProps={{ resizeMode: 'contain' }}
    			actionOnPress={goToCreate}
    			actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
    			{...props}
    		/>
    	);
    };
    
    TaskListEmptyState.displayName = 'TaskListEmptyState';
    const itemsPerPage = 10;
    const [itemsPerPage] = useConfig('tasks.itemsPerPage');
    src/components/TaskList/TaskList.tsx
    import { QueryResult } from '@apollo/client';
    import { Divider } from '@bluebase/components';
    import { getComponent, useConfig } from '@bluebase/core';
    import { GraphqlConnection, GraphqlListProps } from '@bluebase/plugin-json-graphql-components';
    import React from 'react';
    import { ListRenderItemInfo } from 'react-native';
    
    import { Tasks, TasksCollectionQueryQuery, TasksCollectionQueryQueryVariables } from '../../graphql-types';
    import TaskListEmptyState from '../TaskListEmptyState';
    import { TaskListItem, TaskListItemProps } from '../TaskListItem';
    import { TasksCollectionQuery, TasksCollectionQueryUpdateQueryFn } from './TasksCollectionQuery.graphql';
    
    const GraphqlList = getComponent<GraphqlListProps<TaskListItemProps, TasksCollectionQueryQuery>>('GraphqlList');
    
    function mapQueryResultToConnection(result: QueryResult<TasksCollectionQueryQuery>) {
    	return result.data?.tasksCollection as GraphqlConnection<Tasks>;
    }
    
    function renderItem({ item }: ListRenderItemInfo<TaskListItemProps>) {
    	return <TaskListItem {...item} />;
    }
    
    const renderDivider = () => <Divider inset />;
    
    export interface TaskListProps {
    	completed: boolean;
    }
    
    export const TaskList = (props: TaskListProps) => {
    	const { completed } = props;
    	const [itemsPerPage] = useConfig('tasks.itemsPerPage');
    
    	const variables: TasksCollectionQueryQueryVariables = {
    		filter: {
    			completed: { 'eq': completed }
    		}
    	};
    
    	return (
    		<GraphqlList
    			key="task-list"
    			pagination="infinite"
    			itemsPerPage={itemsPerPage}
    			query={TasksCollectionQuery}
    			updateQueryInfinitePagination={TasksCollectionQueryUpdateQueryFn}
    			mapQueryResultToConnection={mapQueryResultToConnection}
    			renderItem={renderItem}
    			ItemSeparatorComponent={renderDivider}
    			ListEmptyComponent={TaskListEmptyState}
    			queryOptions={{
    				variables
    			}}
    		/>
    	);
    };
    
    TaskList.displayName = 'TaskList';
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	defaultConfigs: {
    		'tasks.itemsPerPage': 10,
    	}
    	
    	// ... other properties
    });
    configs.ts
    export const configs = {
    
    	'tasks.itemsPerPage': 5,
    
    	// ... other configs
    };
    src/components/TaskSettingsForm/TaskSettingsForm.tsx
    import { getComponent, useConfig } from '@bluebase/core';
    import { JsonFormProps } from '@bluebase/plugin-json-schema-components';
    import { FormikHelpers } from 'formik';
    import React from 'react';
    
    interface TaskSettingsFormValues {
    	itemsPerPage: number;
    }
    
    const JsonForm = getComponent<JsonFormProps<TaskSettingsFormValues>>('JsonForm');
    
    export interface TaskSettingsFormProps {}
    
    export const TaskSettingsForm = (props: TaskSettingsFormProps) => {
    	const [itemsPerPage, setItemsPerPage] = useConfig('tasks.itemsPerPage');
    
    	return (
    		<JsonForm
    			{...props}
    			schema={{
    				validateOnBlur: false,
    				validateOnChange: false,
    
    				fields: [
    					{
    						autoFocus: true,
    						label: 'Items per page',
    						name: 'itemsPerPage',
    						required: true,
    						type: 'number',
    					},
    					{
    						schema: { component: 'Divider' },
    						type: 'component',
    					},
    					{
    						direction: 'right',
    						name: 'form-actions',
    						type: 'inline',
    
    						fields: [
    							{
    								name: 'reset',
    								type: 'reset',
    							},
    							{
    								name: 'submit',
    								title: 'Save',
    								type: 'submit',
    							},
    						],
    					},
    				],
    
    				initialValues: {
    					itemsPerPage,
    				},
    
    				onSubmit: (values: TaskSettingsFormValues, helpers: FormikHelpers<TaskSettingsFormValues>) => {
    					const { setSubmitting } = helpers;
    					setItemsPerPage(values.itemsPerPage);
    
    					// Wait one second and then setSubmitting to false
    					setTimeout(() => setSubmitting(false), 1000);
    				}
    			}}
    		/>
    	);
    };
    
    TaskSettingsForm.displayName = 'TaskSettingsForm';
    src/components/TaskSettingsForm/index.ts
    export * from './TaskSettingsForm';
    
    import { TaskSettingsForm } from './TaskSettingsForm';
    export default TaskSettingsForm;
    src/filter.ts
    import { SettingsPageProps } from '@bluebase/plugin-settings-app';
    
    import TaskSettingsForm from './components/TaskSettingsForm';
    
    export const filters = {
    	'bluebase.plugin.setting-app.pages': [
    		{
    			key: 'bluebase-settings-todo-page',
    			priority: 20,
    
    			value: (pages: SettingsPageProps[]) => [
    				{
    					name: 'TaskSettings',
    					path: 'task',
    
    					options: {
    						drawerIcon: { type: 'icon', name: 'checkbox-multiple-marked' },
    						title: 'Tasks',
    					},
    
    					items: [
    						{
    							name: 'task-settings',
    							component: TaskSettingsForm,
    							title: 'Task Settings',
    							description: 'Configure your tasks',
    						},
    					],
    				},
    				...pages,
    			],
    		},
    	],
    };
    src/index.ts
    import { createPlugin } from '@bluebase/core';
    
    
    import { filters } from './filters';
    import { lang } from './lang';
    
    export default createPlugin({
    	key: 'tasks',
    	name: 'Tasks',
    	description: 'A todo app made with BlueBase framework.',
    
    	filters: {
    		...filters,
    		...lang,
    	},
    	
    	// ... Other properties
    });
    src/components/CreateTaskForm/CreateTaskMutation.graphql. ts
    import gql from 'graphql-tag';
    
    export const CreateTaskMutation = gql`
    	mutation CreateTaskMutation ($tasks: [tasksInsertInput!]!){
    		insertIntotasksCollection(objects: $tasks) {
    			records {
    				id
    				title
    				description
    				completed
    			}
    		}
    	}
    `;
    yarn graphql-codegen
    src/components/CreateTaskForm/CreateTaskForm.tsx
    import { getComponent, useNavigation } from '@bluebase/core';
    import { JsonGraphqlFormProps } from '@bluebase/plugin-json-graphql-components';
    import React, { useCallback } from 'react';
    
    import { CreateTaskMutationMutationVariables, TasksInsertInput } from '../../graphql-types';
    import { CreateTaskMutation } from './CreateTaskMutation.graphql';
    
    const JsonGraphqlForm = getComponent<JsonGraphqlFormProps<TasksInsertInput>>('JsonGraphqlForm');
    
    export interface CreateTaskFormProps {}
    
    export const CreateTaskForm = (props: CreateTaskFormProps) => {
    	const { navigate } = useNavigation();
    
    	const onSuccess = useCallback(() => {
    		navigate('TasksApp');
    	}, []);
    
    	const mapFormValuesToMutationVariables = useCallback(
    		(task: TasksInsertInput): CreateTaskMutationMutationVariables => {
    			return { tasks: [task] };
    		}, []);
    
    	return (
    		<JsonGraphqlForm
    			mutation={{
    				mutation: CreateTaskMutation
    			}}
    			onSuccess={onSuccess}
    			mapFormValuesToMutationVariables={mapFormValuesToMutationVariables}
    			{...props}
    			schema={{
    				validateOnBlur: false,
    				validateOnChange: false,
    
    				fields: [
    					{
    						autoFocus: true,
    						label: 'Title',
    						name: 'title',
    						required: true,
    						type: 'text',
    					},
    					{
    						label: 'Description',
    						name: 'description',
    						type: 'text',
    					},
    					{
    						label: 'Completed',
    						name: 'completed',
    						type: 'checkbox',
    					},
    					{
    						name: 'status',
    						type: 'status',
    					},
    					{
    						fullWidth: true,
    						name: 'submit',
    						title: 'Create Task',
    						type: 'submit',
    					},
    				],
    			}}
    		/>
    	);
    };
    
    CreateTaskForm.displayName = 'CreateTaskForm';
    src/components/CreateTaskForm/index.ts
    export * from './CreateTaskForm';
    
    import { CreateTaskForm } from './CreateTaskForm';
    export default CreateTaskForm;
    src/screens/CreateTaskScreen/CreateTaskScreen.tsx
    import React from 'react';
    
    import CreateTaskForm from '../../components/CreateTaskForm';
    
    export const CreateTaskScreen = () => {
    	return (
    		<CreateTaskForm />
    	);
    };
    
    CreateTaskScreen.displayName = 'CreateTaskScreen';
    import { createPlugin } from '@bluebase/core';
    
    export const ExamplePlugin = createPlugin({
    
        key: 'example',
        name: 'Example Plugin',
        description: 'This is just an example plugin.',
        version: '1.0.0',
        categories: ['theme', 'app'],
    
        icon: () => ({
            type: 'icon',
            name: 'rocket',
        }),
    
        defaultConfigs: {
            'plugin.example.foo': 'bar'
        },
    
        // ... any components come here
        components: [{
            key: 'Logo',
            value: import('./Logo')
        }],
    
        // ... add other configs, i.e. themes, filters, etc
    });

    Developing a Logger Plugin

    You can add support for any logging service provider by using the following logger filters:

    • bluebase.logger.log

    • bluebase.logger.info

    • bluebase.logger.debug

    • bluebase.logger.warn

    • bluebase.logger.error

    Example

    Developing a Theme Plugin

    A plugin can register any number of themes. To do this simple set the themes property of your plugin class.

    const Plugin = {
    
        themes: [{
            name: 'Material UI (Light)',
            key: 'material-ui-light',
            mode: 'light',
    
            // .. other theme props
        }, {
            name: 'Material UI (Dark)',
            key: 'material-ui-dark',
            mode: 'dark',
    
            // .. other theme props
        }]
    }

    The following guide needs to be added to Themes section.

    Each object in the theme property can be a BlueBase Module. This mean each theme's code can be split into different bundles on web.

    const Plugin = {
    
        public themes = [
            import('path/to/theme-1'),
            import('path/to/theme-2'),
        ]
    }

    Be aware though, that if you use this approach, these bundles will be downloaded during boot time. This is because BlueBase needs know each theme's name and key to store them in registry.

    If you want to split the theme so that they are downloaded only when selected, spilt the internal theme property.

    Making a Plugin Configurable

    Some times developers may need to make their plugins configurable, this may even be necessary in some cases. For example, a Google Analytics plugin may need to have a configuration for tracking code, as this value is different for every project.

    In BlueBase it is very each to achieve this using the Configs API. Just set or get a config value anywhere in the plugin's business logic.

    There are times, when you may need to define default values for plugin configurations as well. To do this, create a defaultConfigs property in your plugin as shown below:

    import { createPlugin } from '@bluebase/core';
    
    export const ExamplePlugin = createPlugin({
    
        key: 'example',
        name: 'Example Plugin',
    
        defaultConfigs: {
            'plugin.example.foo': 'bar'
        },
    
        // ... add other configs, i.e. themes, filters, etc
    });

    BlueBase will automatically set this value in the Config registry, if a custom value isn't provided.

    Plugin Configs Nomenclature

    In BlueBase, we use the following naming convention for configs belonging to plugins:

    So, as shown in the example plugin above, a config foo for a plugin with key example becomes:

    Main App Layout

    BlueBase . It is just a React Component, and can either be used as the top most component of your project or it can be embedded in your existing code base.

    This component takes care of initialisation and renders the SystemApp component.

    If children prop is provided, then it renders the children prop instead of the Main App Layout.

    Plugins

    Plugins are the best way to add features to your BlueBase app.

    It is recommended to keep all plugins in separate NPM packages. This helps us keep all aspects of an app modular. Adding or removing any feature becomes a matter of enabling or disabling a plugin.

    A plugin can be used to do any of the following:

    • Subscribe to application's lifecycle events through Filters and override/modify application behaviour in a non-invasive way.

    • Add new or modify existing Components in the app. Moreover, plugins can wrap any component in Higher Order Components (HOCs).

    • Modify an existing in the app, or extend it to create a new one. It is also possible to create a theme completely from scratch.

    • Add new or modify existing routes to the app.

    Additionally, plugins can be configurable through .

    Lifecycle Events

    BlueBase Boot Lifecycle Events

    • bluebase.boot Internal Filter: bluebase-boot-default (Priority = 5)

      • bluebase.boot.start

        • bluebase.components.register.internal

      • bluebase.configs.load

      • bluebase.components.register

      • bluebase.filters.register

      • bluebase.routes.register

      • bluebase.plugins.register

      • bluebase.plugins.initialize.all

        • bluebase.plugins.initialize

      • bluebase.boot.end

    Components API

    Each component in BlueBase has the following properties:

    Register a Plugin

    There are many ways to register new plugins in BlueBase:

    Through BlueBaseApp Component

    The easiest way to add a new plugin is pass it as a prop to BlueBaseApp.

    Typically, in a large project you would be using the bluebase.ts file to inject props to your main BlueBaseApp component. It is basically same thing as above.

    Filters

    Filters are functions that BlueBase passes data through, at certain points in execution, just before taking some action with the data (such as adding it to the database or rendering some views). Most input and output in BlueBase pass through at least one filter. BlueBase does some filtering by default, and your plugin can add its own filtering.

    The basic steps to add your own filters to BlueBase (described in more detail below) are:

    1. Create the JavaScript function that filters the data.

    2. Hook to the filter in BlueBase, by any of the following methods:

    Accessing Components

    Through resolve method

    In order to access a BlueBase component, just use the resolve method of Component Registry.

    This will return a component wrapped in all the HOCs, and will receive a styles prop that has all the .

    But in order to get access this component in your code, you will need to use a BlueBaseConsumer component to access

    Customise Components

    Theming in BlueBase provides an elaborate mechanism to customise component styles. Following is a list of options to do this in a sequence of lowest to highest priority.

    Component styles in all of the following methods can be thunks as well.

    Through defaultStlyes

    Theme
    Plugin Configs

    Configs

    BlueBase comes with a built-in app configuration system. This is a registry where you can store configurations in key value pairs, as well as subscribe to changes.

    Subscribing to Config updates

    It is also possible to subscribe to

    Theme Structure

    Code Splitting

    TODO: Move code splitting guide from this page to here.

    import { createPlugin } from '@bluebase/core';
    ​
    export const LoggerPlugin = createPlugin({
    ​
        key: 'logger',
        name: 'Logger Plugin',
        categories: ['logging'],
    
        filters: {
            'bluebase.logger.log': (message: string, data: any) => {
                // send data to logging provider here
            }
        }
    });
    class MaterialUIPlugin extends Plugin {
    
        public themes = [{
            name: 'Material UI (Light)',
            key: 'material-ui-light',
            mode: 'light',
    
            theme: import('./theme-rules-light'),
    
            // .. other theme props
        }, {
            name: 'Material UI (Dark)',
            key: 'material-ui-dark',
            mode: 'dark',
    
            theme: import('./theme-rules-dark'),
    
            // .. other theme props
        }]
    }
    plugin.{key}.{config}
    pluign.example.foo

    (optional) (Default = false) Should this component be preloaded at app initialization. This is useful for components that are wrapped in promises (for code splitting).

    hocs

    Array

    An array of React Higher Order Components. At runtime, each component is resolved with wrapping raw component in all HOCs in this array.

    styles

    { [string: key]: Styles }

    An object containing key value pairs of style rules. Each rule maybe a ViewStyle, TextStyle or an ImageStyle.

    source

    ComponentSource

    This property represents the source that registered this component.

    Structure

    Key

    Type

    Description

    key

    string

    The key is used as an ID.

    value

    React.ComponentType

    The raw React component. This may also be a promise (for code splitting) that resolves a React component.

    preload

    boolean

    Through Registry API

    You can add new Plugins anywhere in the code by calling the set method of PluginRegistry:

    Beware though, it is important when a plugin is added to the registry in the system lifecycle. Because all plugins must be initiated, adding them to the registry is not enough.

    Plugins are initialised with the bluebase.plugins.initialize.all filter, which in turn calls the bluebase.plugins.initialize filter for each plugin. So you'll either need to run this filter yourself, or add a plugin before the main filter is executed by the system.

    Even so, to avoid unknown issues it is recommended to use the first method (Through BlueBaseApp Component).

    app.ts
    import { BlueBaseApp } from '@bluebase/core';
    ​
    const ExamplePlugin = {
    ​
        name: 'Example Plugin',
        key: 'example-plugin',
    ​
        // ... other plugin properties
    }
    
    export const App = () => (
      <BlueBaseApp plugins={[ExamplePlugin]} />
    );

    Put your JavaScript function in the filters object of your plugin.

  • Call BB.Filters.register() function.

  • The BlueBase Filters feature is heavily inspired by WordPress Filters.

    Create a Filter Function

    A filter function takes as input the unmodified data and returns modified data (or in some cases, a null value to indicate the data should be deleted or disregarded). If the data is not modified by your filter, then the original data must be returned so that subsequent plugins can continue to modify the value if necessary.

    So, the first step in creating a filter in your plugin is to create a JavaScript function to do the filtering, and put it in your plugin file. For example, if you want to make sure that your posts and comments contain no profanity, you might define a variable with a list of forbidden words, and then create the following function:

    Register your Filter

    After your function is defined, the next step is to register it with BlueBase. There are two ways to do this:

    1. Put the function in the filters property of the plugin

    2 Call BB.Filters.register() function

    where:

    • event The name of a filter event that is provided by the developer. Defines when your filter should be applied.

    • key A unique ID of the function that you want to use for filtering. Keep in mind that other plugins or the BlueBase core may already be using the function key you have thought of, in that case, one filter will overwrite the other.

    • value The filter function. This function may or may not be an async function. The function will always have 3 params:

      1. value: The value that is being filtered

      2. context: An object that may have extra arguments or data, passed at the time of execution.

      3. BB: The BlueBase instance.

    • priority An optional integer argument that can be used to specify the order in which the functions associated with a particular filter are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the filter.

    • Return Value The (optionally modified) value of the first argument is passed to the filter function.

    You can also remove filters from filter hooks using the BB.Filters.delete() function. See Removing Actions and Filters.

    Executing a Filter

    BB.Filters.run function

    You may define and execute your own filters by calling the following method

    where

    • event The name of a filter event

    • value The value that is being filtered

    • context An optional object that may have extra arguments or data

    In the example above, we would put the following params in the function:

    useFilter hook

    Since BB.Filters.run() function returns a Promise, it gets a bit tricky to handle in a component. For this reason we provide a useFilter hook as well:

    where the return value is an object with following properties:

    • value The (optionally modified) value of the first argument is passed to the filter function.

    • loading A flag that indicates if data is currently loading.

    • error If an error occurs, it will be set in this variable.

    Removing Filters

    In some cases, you may find that you want your plugin to disable one of the actions or filters built into BlueBase, or added by another plugin. You can do that by calling BB.Filters.delete('key').

    Note that in general, you shouldn't remove anything unless you know what it does and why it does it -- check the BlueBase or other plugin source code to be sure.

    BB
    context first. This is how the final code may look like:

    This adds unnecessarily long and repetitive code. Not to mention the overall ugliness and unreadability. To solve this problem, we export a helper function called getComponent .

    Through getComponent function

    The getComponent function returns a component from BlueBase context. By using this function we can rewrite the example above as:

    See how this makes the code much more developer friendly.

    It is also possible to get a typed component by providing it props interface:

    To make it even more convenient, we export some basic components out of box. This helps developers not only write even less code, but also port react-native apps to BlueBase by just changing the import statement.

    Fallback Components

    Sometimes, you may need to have fallback components. So if they desired component is not found, a back up component can be used. In BlueBase it is very easy to achieve this. Just provide multiple arguments to the resolve function.

    The first param acts as the key of the desired, with the following ones acting at fallback components in sequence.

    So in the example above, first BlueBase will try to find AnimatedLogo . If it doesn't exist, BlueBase will look for SingleColorLogo . If even that isn't found, it will try to get Logo component.

    This works with the getComponent function. So the code above can be written as:

    Accessing Raw Components

    As explained in the above, the resolve method returns a component wrapped in the registered HOCs, and styles. But sometimes, you may need to access the raw component that was originally registered.

    For that reason, BlueRain provides a getValue method that lets you access the unwrapped “raw” component.

    Note that, we used the await keyword. This is because the getValue method will return the component wrapped in a promise. This is because some components may in different bundles (because of code splitting).

    themed styles
    static prop

    In BlueBase it is possible to provide a defaultStyles static property to a component. This is very similar to the defaultProps concept of React.

    Priority

    In terms of priority, this method has the least priority, and these styles may be overwritten by any of the following methods.

    Use When

    Use this method to define the default styles of the component. These will represent a state of the component without any customisation.

    Through Component Registry

    It is also possible to save styles of a component in the ComponentRegistry.

    When registering a new component

    Overriding styles of an existing component.

    Priority

    In terms of priority, these styles override defaultStyles but they can be overwritten by following methods.

    Use When

    Us this method when you want to define global styles of a component, but you also want other plugins to able to customise them.

    Through theme

    Through styles prop

    Component Styles Structure

    In BlueBase, ComponentStyles have the following structure:

    This is very similar to React Native's StyleSheet.create API.

    Example

    Thunks

    Describe best practices: root, hover, etc.

    const Logo = props => {
      return (
        <View>/* component code */</View>
      )
    }
    ​
    await BB.Components.register({
      key: 'Logo',
      value: Logo,
      preload: true,
      hocs: [withRouter],
      styles: {},
      source: {},
    });
    bluebase.ts
    export default {
    
        // ...other bluebase.ts properties
    
        plugins: [{
    ​
            name: 'Example Plugin',
            key: 'example-plugin',
        ​
            // ... other plugin properties
        }]
    }
    const ExamplePlugin = {
    ​
        name: 'Example Plugin',
        key: 'example-plugin',
    ​
        // ... other plugin properties
    }
    ​
    await BB.Plugins.register(ExamplePlugin);
    await BB.Filters.run('bluebase.plugins.initialize', ExamplePlugin);
    function filter_profanity(content: string) {
    	const profanities = array('badword','alsobad','...');
    	return content.replace(profanities, '{censored}');
    }
    createPlugin({
    	// ... Other properties
    
    	filters: {
    		'post.comment': [{
    			key: 'post-comment-profanity-remover',
    			value: filter_profanity,
    			priority: 2,
    		}]
    	},
    });
    await BB.Filters.register({
    	event: 'post.comment',
    	key: 'post-comment-profanity-remover',
    	value: filter_profanity,
    	priority: 2,
    });
    const newValue = await BB.Filters.run(event, value, [context])
    await BB.Filters.run(
        'post.comment',
        'A comment with profanity',
        { postId: '123' }
    );
    const { value, loading, error } = useFilter(
        'post.comment',
        'A comment with profanity',
        { postId: '123' }
    );
    const Logo = BB.Components.resolve('Logo');
    import { BlueBase, BlueBaseConsumer } from '@bluebase/core';
    
    const Header = () => (
        <BlueBaseConsumer>
        {(BB: BlueBase) => {
            const Logo = BB.Components.resolve('Logo');
            return <Logo />;
        }}
        </BlueBaseConsumer>
    );
    import { getComponent } from '@bluebase/core';
    
    const Logo = getComponent('Logo');
    
    const Header = () => (<Logo />);
    import { getComponent } from '@bluebase/core';
    
    interface LogoProps {
        // ... props
    }
    
    const Logo = getComponent<LogoProps>('Logo');
    - import { Image, Text, View } from 'react-native';
    + import { Image, Text, View } from '@bluebase/components';
    const Logo = BB.Components.resolve('AnimatedLogo', 'SingleColorLogo', 'Logo');
    const Logo = getComponent('AnimatedLogo', 'SingleColorLogo', 'Logo');
    const rawLogoComponent = await BB.Components.getValue('Logo');
    import React from 'react';
    import { Card } from '@bluebase/components';
    
    export class ThemedCard extends React.Component {
    
        static defaultStyles = {
            'root': {
                backgroundColor: 'blue'
            },
            'hover': {
                backgroundColor: 'green'
            }
        }
    
        render() {
            const { styles, isHovering } = this.props;
    
            return (<Card style={isHovering ? styles.hover : styles.root} />);
        }
    }
    class MaterialUIPlugin extends Plugin {
    
        public components = {
            ThemedCard: {
                rawComponent: ThemedCardComponent,
                styles: {
                    root: {
                        backgroundColor: 'orange'
                    }
                }
            }
        }
    }
    BB.Components.setStyles('ThemedCard', {
        root: {
            backgroundColor: 'orange'
        }
    });
    export const theme = {
        // ...other theme props
        components: {
            ThemedCard: {
                root: {
                    backgroundColor: 'yellow'
                }
            }
        }
    };
    const Foo = () => (
        <ThemedCard styles={{
            root: {
                backgroundColor: 'red'
            }
        }} />
    );
    interface ComponentStyles {
        // rule
        [key: string]: ViewStyle | TextStyle | ImageStyle | { [prop: string]: string };
    }
    const styles = {
      root: {
        borderRadius: 4,
        borderWidth: 0.5,
        borderColor: '#d6d7da',
      },
      title: {
        fontSize: 19,
        fontWeight: 'bold',
      },
      activeTitle: {
        color: 'red',
      },
    };

    Registering a Component

    There are many ways to register new components in BlueBase:

    Through a Plugin

    The easiest way to add a new component to BlueBase is to use the 'components' property of your plugin:

    import { View } from '@bluebase/core';
    ​
    const Logo = props => {
      return (
        <View>/* component code */</View>
      )
    }
    ​
    const Plugin = {
    ​
        name: 'Example Plugin',
        key: 'example-plugin',
    ​
        components: {
          Logo
        }
    }

    Note that this may replace any existing components registered with same name.

    Through Registry API

    You can add new Components anywhere in the code by calling the set method of ComponentRegistry:

    Higher Order Components

    A higher-order component's role is to wrap a regular component to pass it a specific prop (such as a list of posts, the current user, the Router object, etc.). You can think of HOCs as specialised assistants that each hand the component a tool it needs to do its job.

    The first argument of set is the component's name, the second is the component itself, and any successive arguments will be interpreted as higher-order components and wrapped around the component.

    For example, this is how you'd pass the currentUser object to the Logo component:

    await BB.Components.register({
        key: 'Logo',
        value: LogoComponent,
        hocs: [withCurrentUser]
    });

    "Delayed" HoCs

    There are a few subtle differences between registering a component with register and the more standard export default Foo and import Foo from './Foo.jsx' ES6 syntax.

    First, you can only override a component if it's been registered using register. This means that if you're building any kind of theme or plugin and would like end users to be able to replace specific components, you shouldn't use import/export.

    Second, both techniques also lead to different results when it comes to higher-order components (more on this below). If you write export withCurrentUser(Foo), the withCurrentUser function will be executed immediately, which will trigger an error because the fragment it depends on isn't properly initialised yet.

    On the other hand, register doesn't execute the function (note that we write withCurrentUser and not withCurrentUser()), delaying execution until app's initialisation.

    But what about HoC functions that take arguments? For example if you were to write:

    The withList(options) would be executed immediately, and you would have no way of overriding the options object later on (a common use case being overriding a fragment).

    For that reason, to delay the execution until the start of the app, you can use the following alternative syntax:

    Add HOCs to existing Components

    You can add new HOCs to the already registered components any time by using the addHocs method.

    Theme Configs

    There are following configuration in BlueBase:

    Themes

    The theme specifies the color of the components, darkness of the surfaces, level of shadow, appropriate opacity of ink elements, etc.

    Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.

    To promote greater consistency between apps, each theme has light and dark variants By default, components use the light theme type.

    Theme provider

    If you wish to customize the theme, you need to use the ThemeProvider

    Customise Themes

    BlueBase supports different types of theme customisation requirements so that a developer can do a single usage customisation or globally override a theme.

    Override all themes

    There may be use cases where you may need to keep specific values same, no matter which theme is selected, i.e. primary colours, etc.

    To do this, use the theme.overrides config. This change is global, and overrides all installed themes.

    Analytics

    The Analytics API enables you to collect analytics data for your app.

    Track Events

    To record an event use the track method of BlueBase's Analytics API.

    Code Splitting

    BlueBase supports code splitting out of the box. It not only takes care of resolving modules, but also manages showing loading and error stated, as well as takes care of server side rendering.

    Please refer to the following links in the docs to learn how code splitting works in different parts of BlueBase:

    • Plugins

    • Components

    Filters
  • Themes

  • BlueBase

    BlueBase ships with 3 system level components. They play a core part in how the framework works.

    1. BlueBaseApp

    2. BlueBaseConsumer

    3. BlueBaseFilter

    Theme overrides. These are applied to all installed themes.

    Key

    Default

    Description

    theme.mode

    light

    This is the theme mode. Can be "light" or "dark".

    theme.name

    bluebase-light

    This is the slug of the selected theme. Changing this value will cause a re-render to all themed components.

    theme.overrides

    Theme

    const Logo = props => {
      return (
        <View>/* component code */</View>
      )
    }
    ​
    await BB.Components.register('Logo', Logo);
    await BB.Components.register({
        key: 'PostsList',
        value: PostsList,
        hocs: [withList(options)]
    });
    component in order to inject a theme into your application. However, this is optional; BlueBase components come with a default theme.

    ThemeProvider relies on the context feature of React to pass the theme down to the components, so you need to make sure that ThemeProvider is a parent of the components you are trying to customize. You can learn more about this in the API section.

    Custom Themes

    Theme builder

    The community has built great tools to build a theme:

    • mui-theme-creator: A tool to help design and customize themes for the MUI component library. Includes basic site templates to show various components and how they are affected by the theme

    • Material palette generator: The Material palette generator can be used to generate a palette for any color you input.

    Specific variation, global usage

    BlueBase ships with two built-in themes: BlueBase Light & BlueBase Dark. You can extend any of the two to create you own theme.

    Specific variation, one time usage

    Using the ThemeProvider overrides prop. This change is only for this tree.

    • Nesting themes

    • Overriding themes

    Nesting Themes

    It is possible to nest multiple themes in a single project. To theme a specific portion of your app, use the ThemeProvider component.

    In the example above, we pass the theme prop to the ThemeProvider component. This prop takes the key of the theme to use for children components. If this prop is not set, the globally selected theme is used.

    Overriding Themes

    The ThemeProvider component can also be used to override a theme for a one time usage.

    Record a Custom Event with Attributes

    The track method lets you add additional attributes to an event. For example, to record artist information with an albumVisit event:

    Record Engagement Metrics

    Data can also be added to an event:

    Integrations

    To add support for an Analytics Provider see this guide.

    BB.Analytics.track({ name: 'albumVisit' });
    BB.Analytics.track({
        name: 'albumVisit', 
        // Attribute values must be strings
        attributes: { genre: '', artist: '' }
    });
    await BB.Components.register({
        key: 'PostsList',
        value: PostsList,
        hocs: [
            [withList, options]
        ]
    });
    BB.Components.addHocs('PostsList', withCurrentUser);
    import { BlueBaseApp, Theme } from '@bluebase/core';
    import React from 'react';
    
    const GreenTheme = new Theme({
    	key: 'green-theme',
    	name: 'Green Theme',
    	light: {
    		palette: {
    			primary: {
    				main: '#00a013',
    				dark: '#007d00',
    				light: '#49bb47',
    			},
    			secondary: {
    				main: '#a0008d',
    				dark: '#830082',
    				light: '#be5cad',
    			}
    		}
    	},
    
    	dark: {
    		palette: {
    			primary: {
    				main: '#00a013',
    				dark: '#007d00',
    				light: '#49bb47',
    			},
    			secondary: {
    				main: '#a0008d',
    				dark: '#830082',
    				light: '#be5cad',
    			}
    		}
    	},
    });
    
    export default function App() {
    	return (
    		<BlueBaseApp themes={[GreenTheme]} />
    	);
    }
    import { BlueBaseApp, Theme } from '@bluebase/core';
    import React from 'react';
    import GreenTheme from './theme';
    
    export default function App() {
    	return (
    		<BlueBaseApp themes={[GreenTheme]} />
    	);
    }
    import { createPlugin } from '@bluebase/core';
    import GreenTheme from './theme';
    
    export default createPlugin({
    	key: 'green-theme-plugin',
    	name: 'Green Theme Plugin',
    
    	themes: [GreenTheme]
    });
    import { buildTheme } from '@bluebase/core';
    
    export const MyCustomTheme = ('light')({
        name: 'MyCustomTheme',
        key: 'my-custom-theme',
        // ... theme props
    });
    import { BlueBaseApp } from '@bluebase/core';
    import { MyCustomTheme } from './MyCustomTheme.ts';
    
    export const App = () => (
        <BlueBaseApp themes={[MyCustomTheme]} />
    );
    bluebase.ts
    const bootOptions = {
    
        configs: {
            'theme.overrides': {
                components: {
                    // component styles
                }
            }
        },
    };
    
    export default bootOptions;
    <View>
        <Text>Default light theme here</Text>
        <ThemeProvider theme="bluebase-dark">
            <View style={{ backgroundColor: theme.palette.background.default }}>
                <Text>Dark theme here</Text>
            </View>
        </ThemeProvider>
    <View>
    <ThemeProvider theme="bluebase-dark" overrides={{ palette: { background: { default: 'red' } } }} >
        {/* All components here will now have a red background color */}
    </ThemeProvider>
    BB.Analytics.track({
        name: 'albumVisit', 
        attributes: {}, 
        metrics: { minutesListened: 30 }
    });

    Registry

    BlueBase uses an internal data structure to store everything.

    Registry

    Item

    Value

    Component

    hocs, source, styles, isAsync, preload

    BlueBaseModule<React.Component>

    Config

    subscriptions

    BlueBaseModule<any> ⁉️

    Filter

    Components

    Render prop component that resolves and returns a value.

    • Automatically re-renders on value change.

    • Shows loading & error states while resolving

    BlueBase Modules

    One of the main purpose of BlueBase is to have all functionality broken up into separate modules. So that adding or removing any feature may become as simple as press of a button.

    Moving forward, from our experience with previous versions, it also became evident that code splitting was a necessity in web environments. And with the latest version, we wanted to support it out of the box.

    So, BlueBase has been rewritten from the ground up to replace fixed pieces of code (i.e. functions or React components) to BlueBase Modules.

    A BlueBase module is any piece of code that:

    • Maybe a CommonJS module or an ES Module.

    • Maybe the module itself, or a that resolves a module.

    Module Types

    A challenge we faced in the earlier versions was the different module types in the Javascript ecosystem. We decided back then that BlueBase must support these out of the box.

    BlueBase supports both Common JS and ES modules. We do this by checking .default property on the imported modules.

    If the .default property does exist, we do module = module.default.

    Dynamic Imports

    Code splitting is achieved through . All major bundlers (e.g. , ) already support it. An import function in dynamic import specification returns a .

    So we check if a module is a , we resolve it automatically, when the module is resolved internally.

    Usage Matrix

    The table below list where BlueBase Modules are used internally, and their behaviour.

    Filters may have individual listeners as promises too.

    Migrating to V8

    Library
    V7
    V8

    @bluebase/plugin-launcher

    v2.1.0

    v2.1.0, v.2.2.0

    @bluebase/plugin-react-navigation

    v2.0.0

    v3.0.0

    @bluebase/plugin-react-router

    v2.0.0

    BlueBaseConsumer 📌

    Since BlueBase is build using React's context functionality, it is possible to access the BlueBase context (often referred to as BB) using the same API used by any other context consumer. This is done by using the BlueBaseConsumer component.

    System Component 📌

    Not Compatible. Use @bluebase/plugin-react-navigation

    @bluebase/plugin-responsive-grid

    0.x

    1.x

    @bluebase/plugin-settings-app

    v5.x

    v6.x

    ActivityIndicator

    ThemeConsumer 📌

    Button

    Yes

    Yes

    Components

    Yes

    Yes

    No

    Assets

    No

    Yes

    No

    Theme

    Yes

    Yes

    No

    Intl

    Yes

    Yes

    No

    Type

    Maybe ES Module

    Maybe Promise

    Must be resolved at boot?

    Plugin

    Yes

    Yes

    Yes

    Filter

    Yes

    Yes

    Yes

    Routes

    Promise
    dynamic imports
    Webpack
    Parcel
    Promise
    Promise

    Yes

    BlueBaseFilter 📌

    name, priority

    BlueBaseModule<Handler>

    Intl ⁉️

    BlueBaseModule<string>

    Plugin

    BlueBaseModule<Plugin>

    Route ⁉️

    Theme

    name, slug, mode, alternate

    BlueBaseModule<Theme>

    This component is shipped with BlueBase Core.

    Usage

    Use it just like any other react component.

    import { BlueBase, BlueBaseConsumer } from '@bluebase/core';
    
    export const CustomComponent = () => (
        <BlueBaseConsumer>
        {(BB: BlueBase) => {
            // Use Context here
        }}
        </BlueBaseConsumer>
    );
    const Foo = () => (
        <RegistryValue registry="Components" key="View">
        {
            (Component) => <Component />; 
        }
        </RegistryValue>
    );

    BlueBaseApp 📌

    This is the main app in the BlueBase framework. It is just a React Component, and can either be used as the top most component of your project or it can be embedded in your existing code base.

    This component takes care of initialisation and renders the ⛩Main App Layout. If children prop is provided, then it renders the children prop instead of the Main App Layout.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Use it just like any other react component.

    Custom Context

    Sometimes, you may want to use custom context. This is especially important when some system functionality may need to be modified before the boot process.

    Just create a new object of BlueBase class. And send pass it to the BlueBaseApp component as BB prop when ready.

    Children

    If children are provided to this component, then the children node is rendered instead of . The child components can use to access BlueBase context.

    Properties

    ErrorState 📌

    Display an error message. Used by UIs when an exception is caught, and an error message needs to be displayed. If in development mode, the actual error is displayed, otherwise displays a generic message in production mode.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    EmptyState 📌

    A generic state used on screens or widgets that are empty or have no data.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    Icon

    BB.Components.Icon

    Not a System Component 📌

    This component is not shipped with BlueBase Core. You can still import it from BlueBase Core, but will need a plugin to register this component.

    Properties

    ComponentState 📌

    A generic component to show different states of a screen or a view. For example, you may need to:

    • Show a loading state when data is loading,

    • Show an empty state when there is not data to show on a screen.

    • Show an error message when an exception occurs during execution.

    These are just a few examples. This component displays a message with an image, a title, a description and a call to action button.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    Styles

    It is possible to customise styles of different elements of this component. To learn about how the styles prop work in BlueBase, see the section.

    DynamicIcon 📌

    An enhanced Icon that can render any of the following:

    • BB.Components.Icon

    • BB.Components.Image

    • A custom component

    JsonSchema 📌

    Renders a Component based on JSON schema. This allows developers to create dynamic layouts in their apps, and even save the schema to databases.

    Moreover, it also makes that schema filter-able. So that any plugin can modify that schema on runtime.

    System Component 📌

    LoadingState 📌

    A component that is used to show a loading state. Shows a spinner by default. If 'timedOut' flag is set then it shows a timeout version.

    System Component 📌

    This component is shipped with BlueBase Core.

    prop

    type

    required

    default

    description

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    components

    ComponentCollection

    no

    -

    Collection of components to add in BlueBase's Component Registry.

    configs

    ConfigCollection

    no

    -

    Collection of configs to add in BlueBase's Config Registry.

    filters

    FilterNestedCollection

    no

    -

    Collection of Filter to add in BlueBase's Filter Registry.

    plugins

    PluginCollection

    no

    -

    Collection of plugins to add in BlueBase's Plugin Registry.

    themes

    ThemeCollection

    no

    -

    Collection of themes to add in BlueBase's Theme Registry.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    BB

    BlueBase

    no

    -

    BlueBase context. If one is not provided a new object will be created.

    children

    number

    no

    -

    ⛩Main App Layout
    BlueBaseConsumer

    If this prop is provided, BlueBase's own App view will not be rendered, and nodes in this prop will be rendered instead.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    error

    Error

    no

    -

    The error to display

    retry

    Function

    no

    -

    Callback function, called when retry button is pressed.

    prop

    type

    required

    default

    description

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    description

    string

    no

    -

    Description Text

    image

    ReactNode

    no

    -

    A ReactNode to show custom UI, if provided, imageSource will be ignored

    imageSource

    string

    no

    -

    Image URL

    title

    string

    no

    -

    Title text

    styles

    ComponentStateStyles

    no

    -

    Style rules

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    Styles of image container view

    root

    ViewStyle

    Main root container styles

    title

    TextStyle

    Title styles

    prop

    type

    required

    default

    description

    actionTitle

    string

    no

    -

    Action title

    actionOnPress

    function

    no

    -

    rule

    type

    description

    actionRoot

    ViewStyle

    Action button container styles

    description

    TextStyle

    Description text styles

    image

    ImageStyle

    Image styles

    imageRoot

    theming

    Action onPress callback function

    ViewStyle

    import { BlueBase, BlueBaseApp } from '@bluebase/core';
    
    export const App = () => (
        <BlueBaseApp
            components={{ /* Components Collection */ }}
            configs={{ /* Configs Collection */ }}
            filters={{ /* Filters Collection */ }}
            plugins={{ /* Plugin Collection */ }}
            themes={{ /* Theme Collection */ }}
        />
    );
    import { BlueBase, BlueBaseApp } from '@bluebase/core';
    
    const BB = new BlueBase();
    
    // Custom business logic on BB object. i.e. Add filters, etc.
    
    export const App = () => (<BlueBaseApp BB={BB} />);
    import { BlueBase, BlueBaseApp } from '@bluebase/core';
    
    export const App = () => (
        <BlueBaseApp>
            {/* This child component will have access to BlueBase context */}
            <CustomComponent />
        </BlueBaseApp>
    );
    import { ErrorState } from '@bluebase/core';
    
    // Then somewhere in your app:
    <ErrorState retry={retryCallback} error={Error('Bang!')} />
    import { EmptyState } from '@bluebase/core';
    
    // Then somewhere in your app:
    <EmptyState/>
    import { ComponentState } from '@bluebase/core';
    
    // Then somewhere in your app:
    <ComponentState
        title="Looks like your'e new here!"
        description="Start by creating your first entry."
        imageSource="https://picsum.photos/200"
        styles={{ image: { width: 100, height: 100 } }}
        actionTitle="Tap to Create"
    />

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Icon

    The following example shows how to use BlueBase Icon component:

    Image

    The following example shows how to use an image as an icon:

    Component

    The following example shows how to use a custom component as an icon:

    It is also possible to use a component registered in the ComponentRegistry by using it's name:

    Properties

    prop

    type

    required

    default

    description

    type

    'component' | 'icon' | 'image'

    yes

    -

    If value is:

    • component: Icon is a custom component, and looks for 'component' prop

    • icon: Icon is an instance of BB.Components.Icon and looks for 'icon' prop

    component

    React Component | string

    -

    -

    This component is shipped with BlueBase Core.

    Usage

    Properties

    prop

    type

    required

    default

    description

    schema

    JsonComponentNode | JsonComponentNode[]

    yes

    -

    JSON Schema

    filter

    string

    no

    -

    Usage

    Properties

    prop

    type

    required

    default

    description

    timedOut

    boolean

    no

    -

    Flag if loading has timedOut.

    retry

    Function

    no

    -

    import { DynamicIcon } from '@bluebase/core';
    
    // Then somewhere in your app:
    <DynamicIcon
        type="icon"
        size={250}
        name="rocket"
    />
    <DynamicIcon
        type="image"
        size={250}
        source={{ uri: 'https://picsum.photos/200' }}
    />
    const CustomComponent = ({ size }: { size: number }) => (
    	<View style={{ height: size, width: size, backgroundColor: 'red' }} />
    );
    
    // Then somewhere in your component:		
    <DynamicIcon
        type="component"
        size={250}
        component={CustomComponent}
    />
    <DynamicIcon
        type="component"
        size={250}
        component="CustomComponent" // equivalent to BB.Components.resolve('CustomComponent')
    />
    import { JsonSchema } from '@bluebase/core';
    
    // Then somewhere in your app:
    <JsonSchema
        filter="content-filter"
        args={{ style: { color: 'blue' } }}
        schema={{
         component: 'Text',
         props: {
             style: {
                 color: 'red'
             }
         },
         text: 'This is the page content.',
     }} />
    import { LoadingState } from '@bluebase/core';
    
    // Then somewhere in your app:
    <LoadingState timedOut={false} retry={retryFunction}/>
    image: Icon is an instance of BB.Components.Image and looks for 'source' prop

    Used when type is 'component'. Either a component or a component name (string). In case of string, it will be fetched from Component Registry.

    name

    string

    -

    -

    Used when type is 'icon'. This is the name prop of the BB.Components.Icon component

    source

    Image Source Prop Type

    -

    -

    Used when type is 'image'. This is the Image source.

    size

    number

    -

    100

    Icon size.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    Event name to filter this schema. If this is not provided, the schema is not filtered.

    args

    object

    no

    -

    Arguments for the filter.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    Callback function when Retry button is pressed.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    Typography

    BlueBase provides following typography components:

    Base:

    • Text (same as Body1)

    Styled Components:

    • H1

    • H2

    • H3

    • H4

    • H5

    • H6

    • Subtitle1

    • Subtitle2

    • Body1

    • Body2

    • Caption

    • Overline

    Icons

    There are 3 Icon types recognised by BlueBase

    • Icon

    • DynamicIcon

    • PluginIcon

    Observers

    BlueBase provides observer components. They observe different aspects of application state so relevant UI state can be rendered.

    There are currently four observer components:

    1. DataObserver

    2. ErrorObserver

    DataObserver 📌

    Observes data to check if it is data is loading, loaded or empty. The resulting flags are passed on to the children function. These flags may be used to show different UIs, i.e. loading state, empty state, etc.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    WaitObserver 📌

    This component is used to do the following:

    • WaitObserver a certain period of time before rendering a component

    • Show timeout state, if the component is visible for a certain time period

    A use case for this can be to show a loading state after waiting a certain period of time for data to load, and if the loading takes too long, show a timeout state.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    HoverObserver 📌

    A React component that notifies its children of hover interactions.

    Initial code taken from: https://github.com/ethanselzer/react-hover-observer.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    isHovering is always false on Android or iOS.

    Properties

    StatefulComponent 📌

    This is a swiss army knife component. Intended to be used as a single source of UI state management. It shows empty, loading, error or data states based on the given props.

    System Component 📌

    This component is shipped with BlueBase Core.

    Usage

    Properties

    HoverObserver
    WaitObserver

    loading

    boolean

    no

    -

    Loading flag.

    data

    any

    no

    -

    Input data.

    children

    ReactNode | Function

    no

    -

    Children, data is passed in the param object of the render prop function. This object is typed as DataObserverChildrenProps.

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    isLoading

    (props) => boolean

    no

    -

    A function used to check if data is loading.

    isEmpty

    (props) => boolean

    no

    -

    A function used to check if data is empty.

    onTimeout

    Function

    no

    -

    A callback function executed when a timeout occurs.

    onRetry

    Function

    no

    -

    A callback function executed when retry method is called from the child component.

    children

    ReactNode | (() => ReactNode)

    no

    -

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    delay

    number

    no

    -

    Delay before rendering a component.

    timeout

    number

    no

    -

    Timeout duration

    onHoverChanged

    (HoverObserverState) => void

    no

    -

    Called with named argument isHovering when isHovering is set or unset.

    onMouseEnter

    Function

    no

    -

    Defaults to set isHovering.

    onMouseLeave

    Function

    no

    -

    Defaults to unsetting isHovering.

    onMouseOver

    Function

    no

    -

    onMouseOut

    Function

    no

    -

    children

    ReactNode | (HoverObserverState) => ReactNode

    no

    -

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    prop

    type

    required

    default

    description

    hoverDelayInMs

    number

    no

    0

    Milliseconds to delay hover trigger.

    hoverOffDelayInMs

    number

    no

    0

    Milliseconds to delay hover-off trigger.

    prop

    type

    required

    default

    description

    testID

    string

    no

    -

    Used to locate this view in end-to-end tests.

    <DataObserver data={dataObj} >
    {
        ({data, loading, empty}) => {
            // Render UI based on params
        }
    }
    </DataObserver>
    <WaitObserver
        delay={1000}
        timeout={3000}
        onTimeout={onTimeout}
        onRetry={onRetry}
        children={(props: WaitObserverChildrenProps) => <LoadingState {...props} />}
    />
    <HoverObserver>
     {({ isHovering }) => (
             <YourChildComponent isActive={isHovering} />
     )}
    </HoverObserver>
    <StatefulComponent data={data} loading={true} delay={200} timeout={10000}>
     <Text>Content</Text>
    </StatefulComponent>