Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8c0091f
Add `useQuery` hook
pvditto Jun 6, 2024
9ab59c9
Add DQL create-react-app example
pvditto Jun 6, 2024
f8eeae8
Add tests
pvditto Jun 6, 2024
dc6d1a8
Add `useExecuteQuery` hook
pvditto Jun 14, 2024
ecedec8
Update `useQuery` to use generics
pvditto Jun 14, 2024
2074928
Update useQuery error type
pvditto Jun 17, 2024
d4d2ee2
Add `test:watch` command
pvditto Jun 17, 2024
7b63f57
Cleanup
pvditto Jun 17, 2024
9fd3db8
Update required Ditto version
pvditto Sep 16, 2024
83365d8
Reenable lazy provider test
pvditto Sep 20, 2024
46cd116
Fix query arguments merging
pvditto Sep 20, 2024
62c6d85
Fix deep comparison on query arguments
pvditto Sep 20, 2024
d987ae4
WIP update example app
pvditto Sep 20, 2024
e44c0b2
Merge master and resolve yarn.lock conflict
pvditto Aug 11, 2025
c70b70a
Rename existing example
pvditto Aug 11, 2025
422facc
Add DQL support with useQuery and useExecuteQuery hooks
pvditto Aug 11, 2025
4f8ab98
fix: update authentication method to use login instead of loginWithToken
pvditto Aug 12, 2025
8f96228
Update Ditto API usage
pvditto Aug 12, 2025
f1daedb
docs: update README to include Ditto authentication setup and usage i…
pvditto Aug 12, 2025
2e46db6
Update logo
pvditto Aug 12, 2025
b61f241
Add comment
pvditto Aug 12, 2025
ea17e85
Clean up
pvditto Aug 12, 2025
13749e7
Fix CI workflow: build main package before examples
pvditto Aug 12, 2025
ec46d04
Update quick start instructions for DQL
pvditto Aug 12, 2025
522cf15
Update README to use DQL hooks (useQuery and useExecuteQuery) instead…
pvditto Aug 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,28 @@ jobs:
- name: 'Setup Chrome'
uses: browser-actions/setup-chrome@latest

- name: 'Install and Build'
- name: 'Install Dependencies'
run: yarn

- name: 'Type Check'
run: yarn types

- name: 'Lint'
run: yarn lint

- name: 'Build Main Package'
run: yarn build

- name: 'Build Example - Vite TypeScript DQL'
run: |
yarn
cd examples/vite-typescript-example
yarn
cd -
yarn types
yarn lint
yarn build

- name: 'Build Example - Vite TypeScript Legacy'
run: |
cd examples/vite-typescript-example-legacy
yarn
yarn build

- name: 'Run Tests'
Expand Down
87 changes: 62 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,28 +253,40 @@ root.render(
)
```

3. In your `App` component, you can now use hooks like `usePendingCursorOperation` or `usePendingIDSpecificOperation` to get your documents like so:
3. In your `App` component, you can now use the DQL hooks `useQuery` and `useExecuteQuery` to interact with your documents:

```tsx
import { usePendingCursorOperation, useMutations } from '@dittolive/react-ditto'
import { useQuery, useExecuteQuery } from '@dittolive/react-ditto'

interface Task {
_id: string
text: string
isCompleted: boolean
}

export default function App() {
const { documents } = usePendingCursorOperation({
collection: 'tasks',
})
// Query documents using DQL
const { documents } = useQuery<Task>('SELECT * FROM tasks WHERE isCompleted = false')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth having a note about type coercion here? I know it is called out in the doc comments for the hook, so perhaps that is good enough


// Execute DQL mutations
const [upsert] = useExecuteQuery<void, { value: Partial<Task> }>(
'INSERT INTO tasks DOCUMENTS (:value) ON ID CONFLICT DO UPDATE'
)

const { removeByID, upsert } = useMutations({ collection: 'tasks' })
const [removeByID] = useExecuteQuery<void, { id: string }>(
'UPDATE tasks SET isDeleted = true WHERE _id = :id'
)

return (
<>
<button onClick={() => upsert({ value: { text: 'Hello' } })}>
<button onClick={() => upsert({ value: { text: 'Hello', isCompleted: false } })}>
Add Task
</button>
<ul>
{documents.map((doc) => (
<li key={doc._id}>
{JSON.stringify(doc.value)}
<button onClick={() => removeByID({ _id: doc.id })}>remove</button>
<li key={doc.value._id}>
{doc.value.text}
<button onClick={() => removeByID({ id: doc.value._id })}>remove</button>
</li>
))}
</ul>
Expand All @@ -283,28 +295,53 @@ export default function App() {
}
```

Alternatively, you can also choose to go with the lazy variants of these hooks (`useLazyPendingCursorOperation` and `useLazyPendingIDSpecificOperation`), in order to launch queries on the data store as a response to a user event:
The `useQuery` hook automatically subscribes to changes and updates your component when the query results change. The `useExecuteQuery` hook returns a function that executes DQL mutations when called. It can also be used to lazily execute non-mutating queries in responce to user actions.

For more complex scenarios, you can also pass parameters to your queries:

```tsx
import { useLazyPendingCursorOperation } from '@dittolive/react-ditto'
import { useQuery, useExecuteQuery } from '@dittolive/react-ditto'

export default function App() {
const { documents, exec } = useLazyPendingCursorOperation()
const [filter, setFilter] = useState<'all' | 'completed' | 'active'>('all')

// Query with parameters
const { documents } = useQuery<Task>(
'SELECT * FROM tasks WHERE (:filter = "all" OR isCompleted = :showCompleted)',
{
args: {
filter,
showCompleted: filter === 'completed'
}
}
)

if (!documents?.length) {
return (
<button onClick={() => exec({ collection: 'tasks' })}>
Click to load!
</button>
)
}
// Update task completion status
const [setCompleted] = useExecuteQuery<void, { id: string, isCompleted: boolean }>(
'UPDATE tasks SET isCompleted = :isCompleted WHERE _id = :id'
)

return (
<ul>
{documents.map((doc) => (
<li key={doc._id}>{JSON.stringify(doc.value)}</li>
))}
</ul>
<div>
<select value={filter} onChange={(e) => setFilter(e.target.value as any)}>
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="active">Active</option>
</select>

<ul>
{documents.map((doc) => (
<li key={doc.value._id}>
<input
type="checkbox"
checked={doc.value.isCompleted}
onChange={(e) => setCompleted({ id: doc.value._id, isCompleted: e.target.checked })}
/>
{doc.value.text}
</li>
))}
</ul>
</div>
)
}
```
Expand Down
26 changes: 26 additions & 0 deletions examples/vite-typescript-example-legacy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

# editors
.idea
40 changes: 40 additions & 0 deletions examples/vite-typescript-example-legacy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Getting Started with Vite

This project was bootstrapped with [Vite](https://vite.dev/).

## Available Scripts

**Note: on newer versions of Node, you may run into `ERR_OSSL_EVP_UNSUPPORTED` errors. You may pass the command-line option of `--openssl-legacy-provider` to work around this. Refer to [Node v17 release notes](https://nodejs.org/es/blog/release/v17.0.0/#openssl-3-0).**

In the project directory, you can run:

### `yarn dev`

Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

The page will reload if you make edits.\
You will also see any lint errors in the console.

### `yarn test`

Launches the test runner in the interactive watch mode.

### `yarn build`

Builds the app for production to the `dist` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.

### `yarn type-check`

Runs the TypeScript type checker

### `yarn lint`

Runs `eslint` to find and fix any configured linting issues

## Learn More

You can learn more in the [Vite documentation](https://vite.dev/guide/).

To learn React, check out the [React documentation](https://react.dev/).
12 changes: 12 additions & 0 deletions examples/vite-typescript-example-legacy/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{
"runtime": "automatic"
}
],
"@babel/preset-typescript"
]
}
62 changes: 62 additions & 0 deletions examples/vite-typescript-example-legacy/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import pluginJs from '@eslint/js'
import prettierPlugin from 'eslint-plugin-prettier/recommended'
import pluginReact from 'eslint-plugin-react'
import pluginReactHooks from 'eslint-plugin-react-hooks'
import simpleImportSort from 'eslint-plugin-simple-import-sort'
import keySort from 'eslint-plugin-sort-keys-fix'
import globals from 'globals'
import tseslint from 'typescript-eslint'

export default [
{
ignores: ['**/dist/*', '**/target/**'],
},
{
files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'],
plugins: {
'simple-import-sort': simpleImportSort,
react: pluginReact,
'react-hooks': pluginReactHooks,
'sort-keys-fix': keySort,
},
settings: {
react: {
version: 'detect',
},
},
rules: {
'no-console': ['error', { allow: ['warn', 'error'] }],
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'sort-imports': 'off',
semi: 0,
'prettier/prettier': [
'error',
{
semi: false,
},
],
...pluginReactHooks.configs.recommended.rules,
},
},
{ languageOptions: { globals: globals.node } },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
prettierPlugin,
pluginReact.configs.flat.recommended,
pluginReact.configs.flat['jsx-runtime'],
{
files: ['**/*.test.{ts,tsx}', '**/__tests__/**'],
languageOptions: { globals: globals.jest },
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
files: ['**/*.config.js'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
]
16 changes: 16 additions & 0 deletions examples/vite-typescript-example-legacy/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Ditto + Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
14 changes: 14 additions & 0 deletions examples/vite-typescript-example-legacy/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Config } from 'jest'

export default {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.css$': 'identity-obj-proxy',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
modulePathIgnorePatterns: ['<rootDir>/dist/'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': 'babel-jest',
},
} satisfies Config
14 changes: 14 additions & 0 deletions examples/vite-typescript-example-legacy/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'

// JSDom, which is used by @testing-library/react, doesn't support TextEncoder and TextDecoder,
// even though they have broad support in modern browsers. This polyfill adds them to the global
// object so that they can be used by @dittolive/ditto.
// cf. https://github.com/jsdom/jsdom/issues/2524
import { TextDecoder, TextEncoder } from 'util'
global.TextEncoder = TextEncoder
// @ts-expect-error: The type mismatch is acceptable
global.TextDecoder = TextDecoder
Loading