Browser Client

Overview

The @interopio/react-hooks library provides custom React hooks for the io.Connect JavaScript libraries - @interopio/browser and @interopio/browser-platform, if you are working on an io.Connect Browser project, or @interopio/desktop, if you are working on an io.Connect Desktop project. The examples below use the @interopio/browser library. The @interopio/react-hooks library allows you to start using io.Connect features in your React apps idiomatically in the context of the React framework.

Prerequisites

The @interopio/react-hooks library comes with the latest version of the @interopio/browser library, but requires the React and ReactDOM libraries to be installed. To install the packages, navigate to the root directory of your project and run:

npm install --save @interopio/react-hooks

Library Features

The @interopio/react-hooks library offers a way to consume the APIs of the @interopio/browser library in your web apps via React Hooks and React Context.

Context

The <IOConnectProvider /> component is a React context provider component. It invokes a factory function (with default or user-defined configuration) which initializes the @interopio/browser library. The io object returned by the factory function is set as the context value.

The following example demonstrates the signature of the <IOConnectProvider /> component:

interface IOConnectProviderProps {
    children: ReactNode;
    settings: IOConnectInitSettings;
    fallback?: NonNullable<ReactNode> | null;
}

interface IOConnectInitSettings {
    browser?: {
        config?: IOConnectBrowser.Config;
        factory?: IOConnectBrowserFactoryFunction;
    };
    browserPlatform?: {
        config?: IOConnectBrowserPlatform.Config;
        factory?: IOConnectBrowserPlatformFactoryFunction;
    };
    desktop?: {
        config?: IOConnectDesktop.Config;
        factory?: IOConnectDesktopFactory;
    };
}

const IOConnectProvider: FC<IOConnectProviderProps>;

The following table describes the properties of the IOConnectInitSettings object:

Property Type Description
browser object Object with two properties: config and factory. The config property accepts a configuration object for the @interopio/browser library. The factory property accepts the factory function exposed by the @interopio/browser library. You should define this object if your app is a Browser Client app in the context of io.Connect Browser.
browserPlatform object Object with two properties: config and factory. The config property accepts a configuration object for the @interopio/browser-platform library. The factory property accepts the factory function exposed by the @interopio/browser-platform library. You should define this object if your app is a Main app app in the context of io.Connect Browser.
desktop object Object with two properties: config and factory. The config property accepts a configuration object for the @interopio/desktop library used in io.Connect Desktop. The factory property accepts the factory function exposed by the @interopio/desktop library. You should define this object if your app is an io.Connect Desktop app.

⚠️ Note that you can't define a browser and browserPlatform property at the same time, but you can define one of them together with desktop. This is useful if you want your app to have different initialization characteristics in io.Connect Browser and io.Connect Desktop.

All properties are optional, but it's recommended that you provide the factory functions explicitly. If no factory functions are provided, the library will try to select the appropriate function attached to the global window object.

The following table describes the properties of the IOConnectProviderProps object:

Property Description
children Required. React components which may contain io.Connect-related logic.
fallback React component to display while initializing io.Connect.
settings Required. Settings object containing the desired factory functions and configuration objects.

IOConnectContext is the React context which is used by the <IOConnectProvider /> component. You can consume this context from anywhere inside you app with the default React hook useContext().

const IOConnectContext: Context<IOConnectBrowser.API | IOConnectDesktop.API>;

Hooks

The useIOConnect() hook is a React hook which will invoke the callback that you pass to it.

The following example demonstrates the signature of useIOConnect():

const useIOConnect: <K = IOConnectBrowser.API | IOConnectDesktop.API, T = void>(
    cb: (io: K, ...dependencies: any[]) => T | Promise<T>,
    dependencies?: any[]
) => T;
Parameter Description
cb Required. Async or sync callback function that will be invoked with the io object and an array of user-defined dependencies. The callback may or may not include io.Connect-related code.
dependencies Array of user-defined variables that will trigger the invocation of the provided callback based on whether the value of any of the specified variables has changed (same functionality as the useEffect() React hook).

The useIOConnectInit() hook is a React hook which initializes the provided io.Connect JavaScript library. It accepts a required settings object, identical to the IOConnectInitSettings object.

type UseIOInitFunc = (
    settings: IOConnectInitSettings
) => IOConnectBrowser.API | IOConnectDesktop.API;

Usage

The following examples demonstrate using the @interopio/react-hooks library.

Initialization

To access the io.Connect APIs, initialize and optionally configure the @interopio/browser library. You can do this in two ways - by using the <IOConnectProvider /> component or the useIOConnectInit() hook. The difference is that the <IOConnectProvider /> initializes the @interopio/browser library and makes the returned io API object globally available by automatically assigning it as a value to IOConnectContext, while the useIOConnectInit() hook initializes the library and returns an io API object which you then have to make available to your other components by passing it as a prop, by creating a context, or by attaching it to the global window object.

Add the <IOConnectProvider /> component by wrapping your other components inside it (preferably the root one). Pass the settings object to the <IOConnectProvider />. It will initialize the @interopio/browser library and make the io.Connect APIs available in your app by setting the returned io object as the value of IOConnectContext:

//In index.js

import IOBrowser from "@interopio/browser";
import { IOConnectProvider } from "@interopio/react-hooks";

ReactDOM.render(
    // Wrap your root component in the `<IOConnectProvider />` in order
    // to be able to access the io.Connect APIs from all child components.
    <IOConnectProvider fallback={<h2>Loading...</h2>} settings={{ browser: { factory: IOBrowser } }}>
        <App />
    </IOConnectProvider>,
    document.getElementById("root")
);

You can also initialize the @interopio/browser library with the useIOConnectInit() hook. The following example demonstrates conditional rendering of a component based on whether the io.Connect API is available:

import IOWorkspaces from "@interopio/workspaces-api";
import { useIOConnectInit } from "@interopio/react-hooks";

const App = () => {
    // Example custom configuration for the io.Connect library.
    const settings = {
        browser: {
            config: {
                libraries: [IOWorkspaces]
            }
        }
    };

    const io = useIOConnectInit(settings);

    return io ? <Main io={io} /> : <Loader />;
};

export default App;

Remember that when you initialize the @interopio/browser library with the useIOConnectInit() hook, you must provide the io object to your nested components to be able the use the io.Connect APIs in them. For example, use React Context or attach it to the global window object.

Consuming io.Connect APIs

After the @interopio/browser library has been successfully initialized, you can access the io.Connect APIs with the built-in React hook useContext() and passing IOConnectContext as its argument, or with the useIOConnect() hook.

⚠️ Note that this library is just a thin wrapper designed to work with both @interopio/browser and @interopio/desktop. For that reason, if you are using React with TypeScript, you should type cast the initialized io object to the appropriate type, because the default type is IOConnectBrowser.API | IOConnectDesktop.API.

The following example demonstrates accessing the io object with IOConnectContext and using the Shared Contexts API to get the context of the current window:

import { useContext, useState, useEffect } from "react";
import { IOConnectContext } from "@interopio/react-hooks";

const App = () => {
    const [context, setContext] = useState({});
    // Access the io.Connect APIs by using the `io` object
    // assigned as a value to `IOConnectContext` by the `<IOConnectProvider />` component.
    const io = useContext(IOConnectContext);

    useEffect(() => {
        setContext(io.windows.my().context);
    }, []);

    return (
        <div>
            <h2>My Window Context</h2>
            <pre>{JSON.stringify(context, null, 4)}</pre>
        </div>
    );
};

export default App;

The following example demonstrates accessing the io object with the useIOConnect() hook and using the Window Management API to open an app in a new window on button click:

import { useIOConnect } from "@interopio/react-hooks";

const App = () => {
    const openWindow = useIOConnect(io => (name, url) => {
        io.windows.open(name, url);
    });

    return (
        <table>
            <tr>
                <td>Client List</td>
                <td>
                    <button
                        onClick={() => {
                            openWindow("ClientList", "http://localhost:8080/client-list");
                        }}
                    >
                        Start
                    </button>
                </td>
            </tr>
        </table>
    );
};

export default App;

The following example demonstrates using the Interop API to get the window title through an already registered Interop method:

import { useIOConnect } from "@interopio/react-hooks";
import { useState } from "react";

const App = () => {
    const [title, setTitle] = useState("");
    const getTitle = useIOConnect(io => methodName => {
        io.interop.invoke(methodName).then(r => setTitle(r.returned._result));
    });
    return (
        <>
            <h2>{title}</h2>
            <button
                onClick={() => {
                    getTitle("MyMethods.GetTitle");
                }}
            >
                Get Title
            </button>
        </>
    );
};

export default App;

Testing

You can use your own factory function for initializing the @interopio/browser library. This is useful in Jest/Enzyme tests when you want to mock the io.Connect library:

//In index.js.

import { mount } from "enzyme";
import { IOConnectProvider } from "@interopio/react-hooks";

// Define a factory function which will mock the io.Connect library.
const ioConnectFactory = () => {
    const ioConnectFactory = {
        interop: { invoke: jest.fn(), register: jest.fn() },
        contexts: { subscribe: jest.fn(), update: jest.fn() },
        windows: { open: jest.fn(), my: jest.fn() }
    };

    return Promise.resolve(ioConnectObject);
};

describe("Mock io.Connect", () => {
    it("Should mock the io.Connect library.", () => {
        const wrapper = mount(
        // Pass your factory function to the `<IOConnectProvider />` component.
        <IOConnectProvider settings={{ browser: { factory: ioConnectFactory} }}>
            <App />
        </IOConnectProvider>
        );
        // Your logic here.
    });
});

For additional information on testing React hooks, see the @testing-library/react-hooks.