Skip to main content

React SDK

A library for integrating Squid with React.

Features

  • Hooks for access to the Squid Client's collections, documents, and queries.
  • A provider for access to the Squid Client anywhere in your React components.

Getting started

Requirements

This SDK requires a minimum React version of 16.11.

Installation

Install the Squid React SDK using NPM:

npm install @squidcloud/react

Configure Squid

  1. Create an Application by navigating to the Squid Console and clicking Create application.

  2. Copy the Application ID from the overview tab of the new Application.

  3. Add the following provider to your React application:

// main.tsx

import { SquidContextProvider } from '@squidcloud/react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
<SquidContextProvider
options={{
appId: '<SQUID_CLOUD_APP_ID>',
region: '<SQUID_CLOUD_REGION>',
}}
>
<App />
</SquidContextProvider>
);

Note: If you're using a .env file for environment management, set the appId and region to your preferred envars:

// main.tsx

<SquidContextProvider
options={{
appId: process.env.SQUID_CLOUD_APP_ID,
region: process.env.SQUID_CLOUD_REGION,
}}
>

Hooks

Wrapping your application in a SquidContextProvider provides the app with access to a Squid instance. To directly reference this instance, use the useSquid hook.

App.tsx
function App() {
const squid = useSquid();

const foo = () => {
squid.executeFunction('foo');
};

return <button onClick={foo}>Foo</button>;
}

However, there are some additional hooks to provide access to collections, queries and documents.

useCollection

The useCollection hook is a wrapper around squid.collection(...). It allows you to access a collection without needing a squid reference. Once you have a collection, you can use the collection to create queries and manage documents.

const collection = useCollection<User>('users');

const query = collection.query().eq('name', 'John Doe');
const doc = collection.doc('user-id');

useQuery

When a query has been created, you use the useQuery hook to execute it, and optionally subscribe to the results.

The hook returns an object that includes the following properties:

  • loading: Whether data has been returned by the query.
  • data: The query results as an array. This may be document references, document data, join query results, etc, depending on the type of query that is passed to the hook.
  • error: The error object, if an error occurs while executing the query.
App.tsx
function App() {
const collection = useCollection<User>('users');

/**
* The list of docs will be streamed to the client and will be
* kept up-to-date.
*/
const { docs } = useQuery(collection.query().gt('age', 18), {
subscribe: true,
initialData: [],
});

return (
<ul>
{docs.map((d) => (
<li key={d.refId}>{d.data.age}</li>
))}
</ul>
);
}

If the subscribe option is set to true, data will be streamed to the client and the component will automatically re-render when new updates are received. If subscribe is false, the initial data is fetched for the query, but no changes are streamed.

Optionally, the hook can also accept an initialData option that will be returned until the first result is loaded.

usePagination

The usePagination hook is used to paginate through query results. It provides an interface to handle pagination and keep the data up-to-date with changes. For more information on pagination in Squid, check out our pagination documentation.

The hook returns an object that includes the following properties:

  • loading: Whether data is currently being loaded or paginated.
  • data: The paginated results as an array. This may be document references, document data, join query results, etc, depending on the type of query that is passed to the hook.
  • hasNext: A boolean indicating if there are more results available after the current page.
  • hasPrev: A boolean indicating if there are more results available before the current page.
  • next: A function to load the next page of results (only active when hasNext is true).
  • prev: A function to load the previous page of results (only active when hasPrev is true).
App.tsx
function App() {
const collection = useCollection<User>('users');

/**
* Paginate through the list of users.
* The list of docs will be streamed to the client and will be kept up-to-date.
*/
const { docs, loading, hasNext, hasPrev, next, prev } = usePagination(
collection.query().eq('age', 30).sortBy('name'),
{ subscribe: true, pageSize: 10 } /* PaginationOptions */,
[30] // deps
);

if (loading) {
return <div>Loading...</div>;
}

return (
<div>
<ul>
{docs.map((d) => (
<li key={d.refId}>{d.data.name}</li>
))}
</ul>
<button onClick={prev} disabled={!hasPrev}>
Previous
</button>
<button onClick={next} disabled={!hasNext}>
Next
</button>
</div>
);
}

If the subscribe option is set to true, data will be streamed to the client and the component will automatically re-render when new updates are received. If new data is added between the first and last item on your page, your page will automatically update to show the new data, ensuring that only pageSize items are visible.

To use pagination, your query must specify a sortBy.

Optionally, the hook can also accept a deps array. When the deps array changes, a new pagination query will be created.

useDoc

The useDoc hook provides similar functionality, but instead of subscribing to a query, you subscribe to updates of a particular document.

The hook returns an object that includes the following properties:

  • loading: Whether data has been returned by the document query.
  • data: The document data. This can be undefined if no data has been received or if the document has been deleted.
  • error: The error object, if an error occurs while querying for the document.
// App.tsx

function App() {
const collection = useCollection<User>('users');
const doc = collection.doc('user-id');

/**
* Changes to the doc will be streamed to the client and it will be
* kept up-to-date.
*/
const { data } = useDoc(doc);

return <span>{data.foo}</span>;
}

useDocs

The useDocs hook provides updates for multiple document references.

The hook returns an object that includes the following properties:

  • loading: Whether data has been returned by all document queries.
  • data: An array of document data. An element in the array can be undefined if no data has been received or if the document has been deleted.
  • error: The error object, if an error occurs while querying for any of the documents.
App.tsx
// App.tsx

function App() {
const collection = useCollection<User>('users');
const docs = [collection.doc('my-id-1'), collection.doc('my-id-2')];

/**
* Changes to the documents will be streamed to the client and they will be
* kept up-to-date.
*/
const { data } = useDocs(docs);

return (
<ul>
<li>{data[0].foo}</li>
<li>{data[1].foo}</li>
</ul>
);
}

Async Hooks

The Squid Client SDK uses Promises and Observables, however some work needs to be done to support these kinds of asynchronous updates in React components.

To ensure that all Squid Client SDK functionality can be easily integrated into React, Squid exposes the usePromise and useObservable hooks. These hooks allow you to use asynchronous functions on the squid instance directly in your React components.

useObservable

The useObservable hook accepts a function that returns an Observable<T>, allowing you to subscribe to the observable and receive updates within your component. It returns an object that includes the following properties:

  • loading: Whether a value has been received from the observable.
  • data: The most recent data received from the observable.
  • error: The error object, if the observable encounters an error.
  • complete: Whether the observable has completed.
function App() {
const [bar, setBar] = useState('bar');
const squid = useSquid();

const { loading, data, error, complete } = useObservable(
() => {
return squid.collection<User>('users').query().gt('foo', bar).snapshots();
},
{ initialData: [] },
[bar] // deps
);
}

Optionally, the hook can also accept an initialData option (defaults to null) and a deps array. When the deps array changes, the current observable will unsubscribe, and a new subscription will be created. In the example above, you can see the where condition of the query relies on the bar variable. To ensure that the query is properly updated when bar changes, we need to pass it as a dependency.

Whenever the deps change, loading is reset to true until a value is emitted from the newly created observable.

usePromise

The usePromise hook is similar to useObservable, except it accepts a function that returns a Promise<T>. The reason the hook takes a function instead of a promise directly is to ensure that the promise does not start executing until the component mounts.

  • loading: Whether the promise has resolved or rejected.
  • data: The data resolved by the promise.
  • error: The error object, if the promise rejected.
function App() {
const [bar, setBar] = useState('bar');
const squid = useSquid();

const { loading, data, error } = usePromise(
() => {
return squid.collection<User>('users').query().gt('foo', bar).snapshot();
},
{ initialData: [] },
[bar] // deps
);
}

The hook can also take an initialData option (defaults to null) and deps array. Whenever the deps change, the result of the ongoing promise will be ignored, and a new promise will be created. In the example above, a new promise is created each time the bar variable changes.

Feature Hooks

In addition to the hooks provided above, the Squid React SDK also provides some hooks that are designed to simplify some common Squid use cases.

useAiChatbot

The useAiChatbot hook wraps the AI Chatbot to allow for easier question asking and access to chat history. The hook can be used as follows:

const { chat, history, data, loading, complete, error } = useAiChatbot(
'integration-id',
'profile-id'
);

and returns the following:

  • chat: A function that accepts a prompt for the AI agent (as a string).
  • history: An array of messages. The messages have an id, message and type that is either ai or user, and include the active response from the AI agent.
  • data: The current response (if a chat is in progress).
  • loading: Whether we're waiting on a response from the current prompt.
  • complete: Whether the current response has finished.
  • error -> Populated when an error has been returned for the current prompt.
const Chat = () => {
const [value, setValue] = useState('');
const { history, chat, complete } = useAiChatbot(
'integration-id',
'profile-id'
);

const handleClick = () => {
chat(value);
};

return (
<>
<input onChange={(e) => setValue(e.target.value)} value={value} />
<button onClick={handleClick} disabled={!complete}>
Chat
</button>
{history.map(({ id, message, type }) => (
<span key={id}>
{type}: {message}
</span>
))}
</>
);
};