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
Create an Application by navigating to the Squid Console and clicking Create application.
Copy the
Application ID
from the overview tab of the new Application.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.
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.
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 whenhasNext
is true).prev
: A function to load the previous page of results (only active whenhasPrev
is true).
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
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 anid
,message
andtype
that is eitherai
oruser
, 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>
))}
</>
);
};