Key Concepts
What's a Room?
A Room is a self-contained workspace where users can explore datasets, run queries, and view results. The term comes from collaborative tools—where users work in shared spaces—and SQLRooms is built with future real-time collaboration in mind.
A Room consists of:
<RoomShell>
: a React component that renders the Room UIroomStore
: a Zustand-based state store for the Room
Room Store
The roomStore
is a composable Zustand
store created by calling createRoomStore()
. The store holds:
config
: the persistable part of the state that captures a Room's saveable settings and can be serialized to JSON for storage or sharing including:- the view configuration and the layout state
- the user preferences
room
: non-persistable state that holds runtime information like:- loaded DuckDB tables
- transient UI state (like "query running")
const {roomStore, useRoomStore} = createRoomStore<RoomConfig, RoomState>(
(set, get, store) => ({
...createRoomShellSlice<RoomConfig>({
config: {
dataSources: [
{
type: 'url',
url: 'https://.../earthquakes.parquet',
tableName: 'earthquakes',
},
],
},
room: {
// Runtime state initialization…
},
})(set, get, store),
}),
);
Check the minimal example for the complete implementation.
RoomShell
<RoomShell>
is a React component that wraps your Room UI
- It injects the
roomStore
into React context, accessible via theuseRoomStore()
hook - It sets up essential UI infrastructure including error boundaries, toast notifications, and tooltips, making it easy to use components from
@sqlrooms/ui
out of the box - It provides slots for the optional
LayoutComposer
(see Layout section below),Sidebar
, andLoadingProgress
components
const App = () => (
<RoomShell roomStore={roomStore}>
<MyComponent />
</RoomShell>
);
SQL and DuckDB Access
SQLRooms includes a built-in DuckDB integration via the DuckDbSlice
. The DuckDbSlice
provides helper functions for managing and querying tables:
findTableByName()
- Look up a table by name in the current schemaaddTable()
- Add a new table from Arrow data or recordsdropTable()
- Remove a table from the databaserefreshTableSchemas()
- Update the cached table schemastables
- The cached list of tables from the last refreshTableSchemas() callgetConnector()
- Access the underlying DuckDB connector
You can query your datasets using the useSql(query)
hook and work directly with Arrow tables in React.
function MyComponent() {
const isTableReady = useRoomStore((state) =>
Boolean(state.db.findTableByName('earthquakes')),
);
const queryResult = useSql<{maxMagnitude: number}>({
query: `SELECT max(Magnitude) AS maxMagnitude FROM earthquakes`,
enabled: isTableReady,
});
const row = queryResult.data?.toArray()[0];
return row ? `Max earthquake magnitude: ${row.maxMagnitude}` : <Spinner />;
}
For more details on DuckDB integration and available methods, see the DuckDB API Reference.
Composing Store from Slices
The store can be enhanced with slices—modular pieces of state and logic that can be added to your Room. You can use slices from the @sqlrooms/*
packages or create your own custom slices. Each slice is a function that returns a partial state object along with methods to modify that state.
Here's an example showing how to combine the default room shell with SQL editor functionality:
const {roomStore, useRoomStore} = createRoomStore<RoomConfig, RoomState>({
// Default slice
...createRoomShellSlice<RoomConfig>({
config: {
// Add SQL editor slice persistable config
...createDefaultSqlEditorConfig(),
},
room: {},
})(set, get, store),
// Mix in sql editor slice
...createSqlEditorSlice()(set, get, store),
});
You can access slices' namespaced config, state and functions in the store using selectors, for example:
const queries = useRoomStore((state) => state.config.sqlEditor.queries);
const runQuery = useRoomStore((state) => state.sqlEditor.parseAndRunQuery);
Learn more about store and slices in State Management.
Layout (Optional)
The LayoutComposer
provides a flexible panel layout for your Room's UI.
- Panels are React components that can be plugged into the layout. They include metadata (
id
,title
,icon
) and acomponent
to render. - Panels can be moved, resized, or hidden
- Developers can add panels by registering them in the
roomStore
. - Layout state is persisted in the
roomStore
Configure the room layout and panels during store initialization:
const {roomStore, useRoomStore} = createRoomStore<RoomConfig, RoomState>(
(set, get, store) => ({
...createRoomShellSlice<RoomConfig>({
config: {
layout: {
type: LayoutTypes.enum.mosaic,
nodes: {
// Data panel on left (30%) and main view on right
direction: 'row',
first: 'data-panel',
second: MAIN_VIEW,
splitPercentage: 30,
},
},
},
room: {
// Define the available panels in the room layout
panels: {
'data-panel': {
title: 'Data Sources',
icon: DatabaseIcon,
component: DataSourcesPanel,
placement: 'sidebar',
},
main: {
title: 'Main view',
icon: () => null,
component: MainView,
placement: 'main',
},
},
},
})(set, get, store),
}),
);
Layout composer renders the mosaic layout with panels:
function App() {
return (
<RoomShell className="h-screen" roomStore={roomStore}>
<RoomShell.Sidebar />
<RoomShell.LayoutComposer />
</RoomShell>
);
}
For more details on layout configuration and customization, see the Layout API Reference.