# React

Source: https://docs.interop.io/browser/developers/browser-client/react/index.html

## Overview

The `@interopio/react-hooks` library is a lightweight wrapper for the io.Connect JavaScript libraries - [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) for **io.Connect Desktop** projects, and [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) and [`@interopio/browser-platform`](https://www.npmjs.com/package/@interopio/browser-platform) for **io.Connect Browser** projects. It provides custom React components, contexts, and hooks that can be used to initialize the io.Connect libraries and access the io.Connect APIs in your React apps.

The following examples use the `@interopio/browser` library.

## Installation

To install the [`@interopio/react-hooks`](https://www.npmjs.com/package/@interopio/react-hooks) library, execute the following command:

```cmd
npm install @interopio/react-hooks
```

For the library to operate properly in a Browser Client app, you must also install the following optional peer dependencies:

- The [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library.
- The React and ReactDOM libraries.

## Library Features

The [`@interopio/react-hooks`](https://www.npmjs.com/package/@interopio/react-hooks) library offers a way to consume the APIs of the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library in your web apps via [React Hooks](https://reactjs.org/docs/hooks-intro.html) and [React Context](https://reactjs.org/docs/context.html).

### Context

- #### IOConnectProvider

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`](https://www.npmjs.com/package/@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:

```typescript
interface IOConnectProviderProps {
    children: ReactNode;
    settings: IOConnectInitSettings;
    fallback?: NonNullable<ReactNode> | null;
    onInitError?: (error: Error) => void;
}

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`](https://www.npmjs.com/package/@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`](https://www.npmjs.com/package/@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](https://docs.interop.io/browser/developers/browser-platform/overview/index.md) 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`](https://www.npmjs.com/package/@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 injected in 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. |
| `onInitError` | Callback that will be invoked if the io.Connect library initialization fails. *Available since **io.Connect Browser** 3.3.* |
| `settings` | **Required.** Settings object containing the desired factory functions and configuration objects. |

- #### IOConnectContext

`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()`.

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

### Hooks

- #### useIOConnect()

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()`:

```typescript
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()`](https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects) React hook). |

- #### useIOConnectInit()

The `useIOConnectInit()` hook is a React hook which initializes the provided io.Connect JavaScript library. It accepts a required `IOConnectInitSettings` object as a first argument and an optional `onInitError` callback as a second that will be invoked if the io.Connect library initialization fails.

```typescript
type UseIOInitFunc = (
    settings: IOConnectInitSettings,
    // Available since io.Connect Browser 3.3.
    onInitError?: (error: Error) => void
) => IOConnectBrowser.API | IOConnectDesktop.API;
```

## Usage

The following examples demonstrate using the [`@interopio/react-hooks`](https://www.npmjs.com/package/@interopio/react-hooks) library.

### Initialization

To access the io.Connect APIs, initialize and optionally configure the [`@interopio/browser`](https://www.npmjs.com/package/@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 />` component initializes the `@interopio/browser` library and makes the returned API object (`io`) globally available by automatically assigning it as a value to `IOConnectContext`, while the `useIOConnectInit()` hook initializes the library and returns an 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.

> ⚠️ *Note that the [`@interopio/react-hooks`](https://www.npmjs.com/package/@interopio/react-hooks) library is just a thin wrapper designed to work with the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) and the [`@interopio/desktop`](https://www.npmjs.com/package/@interopio/desktop) library. For that reason, if you are using React with TypeScript, you should type cast the initialized io.Connect API object (`io`) to the appropriate type. The default type is `IOConnectBrowser.API | IOConnectDesktop.API`.*

- #### IOConnectProvider

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`:

```javascript
import { createRoot } from "react-dom/client";
import IOBrowser from "@interopio/browser";
import { IOConnectProvider } from "@interopio/react-hooks";

// Settings for the internal initialization of the `@interopio/browser` library.
const settings = {
    browser: { factory: IOBrowser }
};

const domElement = document.getElementById("root");
const root = createRoot(domElement);

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

- #### useIOConnectInit()

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:

```javascript
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`](https://www.npmjs.com/package/@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`.*

- #### IOConnectContext

The following example demonstrates accessing the `io` object with `IOConnectContext` and using the [Shared Contexts API](https://docs.interop.io/browser/reference/javascript/shared%20contexts/api/index.md) to get the context of the current window:

```javascript
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(await io.windows.my().getContext());
    }, []);

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

export default App;
```

- #### useIOConnect()

The following example demonstrates accessing the `io` object with the `useIOConnect()` hook and using the [Window Management API](https://docs.interop.io/browser/reference/javascript/windows/api/index.md) to open an app in a new window on button click:

```javascript
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](https://docs.interop.io/browser/reference/javascript/interop/api/index.md) to get the window title through an already registered Interop method:

```javascript
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;
```

### io.Connect API Usage Recommendations

Consider the following io.Connect API specifics and usage recommendations in relation to the lifecycle of the React components and hooks.

The initialized io.Connect API object (`io`) will be exactly the same in every re-render of a React component and won't cause re-evaluation of the code if used in a dependency array of a React hook.

When [registering Interop methods](https://docs.interop.io/browser/capabilities/data-sharing/interop/index.md#method_registration) in your React app, consider the lifecycle of the React components and design the method registration and unregistration process accordingly. If a component attempts to register the same Interop method more than once (e.g., when re-rendered because of props or state updates) without unregistering it first, this will cause the io.Connect API to throw an error. The same will happen if you attempt to register the same Interop method from different components in your React app.

If your component will be rendered more than once, you can use the `useEffect()` hook to handle the registration and unregistration of your Interop method. When using `useEffect()` (or any other React hook), consider passing an empty dependency array, or a minimal amount of dependencies in order to avoid unnecessary cycles of registering and unregistering Interop methods because of component re-renders. If you need certain Interop methods to be available throughout the entire lifetime of your app, you should register them in components that will never be unmounted.

The following example demonstrates registering an Interop method using the `useEffect()` hook:

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

const App = () => {
    const io = useContext(IOConnectContext);

    // Registering an Interop method in a `useEffect()` hook.
    useEffect(() => {​
        const methodName = "Addition";
        const methodHandler = (a, b) => a + b;
        const registerMethod = async () => await io.interop.register(methodName, methodHandler)​;
        const unregisterMethod = () => io.interop.unregister(methodName);

        registerMethod();
    ​
        // Return a cleanup function that will unregister the Interop method when the component unmounts.
        return unregisterMethod;

    // Using an empty dependency array to instruct the hook to run only once on initial render
    // to avoid unnecessary cycles of method registration and unregistration during re-renders.
    }, [])
};

export default App;
```

When subscribing for updates of any io.Connect context type (e.g., [shared context](https://docs.interop.io/browser/capabilities/data-sharing/shared-contexts/index.md#subscribing_for_context_updates), [Channel context](https://docs.interop.io/browser/capabilities/data-sharing/channels/index.md#subscribing_for_channel_updates), [Workspace context](https://docs.interop.io/browser/reference/javascript/workspaces/workspace/index.md#Workspace-onContextUpdated), [window context](https://docs.interop.io/browser/capabilities/windows/window-management/index.md#window_events-context_update)), consider the lifecycle of the React components and design the subscribing and unsubscribing process accordingly. Keep in mind that the io.Connect API won't prevent the same component from subscribing to the same context object multiple times. Each time the context is updated, all registered update handlers will be invoked. This means that if your component will be rendered more than once (e.g., because of props or state updates), you should unsubscribe from receiving context updates when the component is unmounted. This will avoid potential performance problems caused by creating too many subscriptions because of re-renders.

You can use the `useEffect()` hook to handle subscribing and unsubscribing for context updates. When using `useEffect()` (or any other React hook), consider passing an empty dependency array, or a minimal amount of dependencies in order to avoid unnecessary cycles of subscribing and unsubscribing for context updates. If you need certain subscriptions to contexts to be available throughout the entire lifetime of your app, you should subscribe from components that will never be unmounted.

The following example demonstrates subscribing to a shared context using the `useEffect()` hook:

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

const App = () => {
    const io = useContext(IOConnectContext);

    // Subscribing to a shared context in a `useEffect()` hook.
    useEffect(() => {​
        let unsubscribe;
        const contextName = "MyContext";
        const subscriptionHandler = (update) => console.log(`Context data was updated: ${update}`);
        const subscribe = async () => {
            unsubscribe = await io.contexts.subscribe(contextName, subscriptionHandler)​;
        };

        subscribe();
    ​
        // Return a cleanup function that will unsubscribe from the context when the component unmounts.
        return unsubscribe;

    // Using an empty dependency array to instruct the hook to run only once on initial render
    // to avoid unnecessary cycles of subscribing and unsubscribing during re-renders.
    }, [])
};

export default App;
```

### Testing

You can use your own factory function for initializing the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library. This is useful in Jest/Enzyme tests when you want to mock the io.Connect library:

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

// Define a factory function which will mock the io.Connect library.
const ioConnectFactory = () => {
    const ioConnectObject = {
        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 more details on testing React hooks, see the [`@testing-library/react-hooks`](https://www.npmjs.com/package/@testing-library/react-hooks) library.*

## io.Connect JavaScript Capabilities

Once the `@interopio/react-hooks` library has been initialized, your Browser Client app has access to all io.Connect functionalities. For more detailed information on the different io.Connect capabilities and APIs, see the following sections:

- [Shared Contexts](https://docs.interop.io/browser/capabilities/data-sharing/shared-contexts/index.md)
- [Channels](https://docs.interop.io/browser/capabilities/data-sharing/channels/index.md)
- [Interop](https://docs.interop.io/browser/capabilities/data-sharing/interop/index.md)
- [Intents](https://docs.interop.io/browser/capabilities/data-sharing/intents/intents-api/index.md)
- [Window Management](https://docs.interop.io/browser/capabilities/windows/window-management/index.md)
- [Workspaces](https://docs.interop.io/browser/capabilities/windows/workspaces/workspaces-api/index.md)
- [Layouts](https://docs.interop.io/browser/capabilities/windows/layouts/layouts-api/index.md)
- [Themes](https://docs.interop.io/browser/capabilities/windows/themes/index.md)
- [Modals](https://docs.interop.io/browser/capabilities/windows/modals/modals-api/index.md)
- [App Management](https://docs.interop.io/browser/capabilities/app-management/index.md)
- [App Preferences](https://docs.interop.io/browser/capabilities/app-preferences/index.md)
- [Plugins](https://docs.interop.io/browser/capabilities/plugins/setup/index.md)
- [Notifications](https://docs.interop.io/browser/capabilities/notifications/notifications-api/index.md)
- [Search](https://docs.interop.io/browser/capabilities/search/search-api/index.md)
