-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#863 - Add @tomic-react docs and collectionPage hook
- Loading branch information
Showing
15 changed files
with
488 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -524,6 +524,7 @@ export function useCanWrite( | |
|
||
if (resource.new) { | ||
setCanWrite(true); | ||
setMsg(undefined); | ||
|
||
return; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Collection } from '@tomic/lib'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
export function useCollectionPage(collection: Collection, page: number) { | ||
const [items, setItems] = useState<string[]>([]); | ||
|
||
useEffect(() => { | ||
collection.getMembersOnPage(page).then(setItems); | ||
}, [collection, page]); | ||
|
||
return items; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Examples | ||
|
||
## Realtime Todo app | ||
|
||
In this example, we create a basic to-do app that persists on the server and updates in real-time when anyone makes changes. | ||
If you were to make this in vanilla react without any kind of persistence it would probably look almost the same. | ||
The main difference is the use of the `useArray` and `useBoolean` hooks instead of `useState`. | ||
|
||
```jsx | ||
import { useArray, useBoolean, useResource } from '@tomic/react'; | ||
import { useState } from 'react'; | ||
|
||
export const TodoList = () => { | ||
const store = useStore(); | ||
const checklist = useResource<Checklist>('https://my-server/checklist/1'); | ||
|
||
const [todos, setTodos] = useArray(checklist, todoApp.properties.todos, { | ||
commit: true, | ||
}); | ||
|
||
const [inputValue, setInputValue] = useState(''); | ||
|
||
const removeTodo = (subject: string) => { | ||
setTodos(todos.filter(todo => todo !== subject)); | ||
}; | ||
|
||
const addTodo = async () => { | ||
const newTodo = await store.newResource({ | ||
isA: todoApp.classes.todoItem, | ||
parent: checklist.subject, | ||
propVals: { | ||
[core.properties.name]: inputValue, | ||
[todoApp.properties.done]: false, | ||
}, | ||
}); | ||
|
||
await newTodo.save(); | ||
|
||
setTodos([...todos, newTodo.subject]); | ||
setInputValue(''); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<ul> | ||
{todos.map(subject => ( | ||
<li key={subject}> | ||
<Todo subject={subject} onDelete={removeTodo} /> | ||
</li> | ||
))} | ||
</ul> | ||
<input | ||
type='text' | ||
placeholder='Add a new todo...' | ||
value={inputValue} | ||
onChange={e => setInputValue(e.target.value)} | ||
/> | ||
<button onClick={addTodo}>Add</button> | ||
</div> | ||
); | ||
}; | ||
|
||
interface TodoProps { | ||
subject: string; | ||
onDelete: (subject: string) => void; | ||
} | ||
|
||
const Todo = ({ subject, onDelete }: TodoProps) => { | ||
const resource = useResource<Todo>(subject); | ||
const [done, setDone] = useBoolean(resource, todoApp.properties.done, { | ||
commit: true, | ||
}); | ||
|
||
const deleteTodo = () => { | ||
resource.destroy(); | ||
onDelete(subject); | ||
}; | ||
|
||
return ( | ||
<span> | ||
<input | ||
type='checkbox' | ||
checked={done} | ||
onChange={e => setDone(e.target.checked)} | ||
/> | ||
{resource.title} | ||
<button onClick={deleteTodo}>Delete</button> | ||
</span> | ||
); | ||
}; | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# useCanWrite | ||
|
||
`useCanWrite` is a hook that can be used to check if an agent has write access to a certain resource. | ||
|
||
Normally you would just use `await resource.canWrite()` but since this is an async function, using it in react can be annoying. | ||
|
||
The `useCanWrite` hook works practically the same as the `canWrite` method on `Resource`. | ||
|
||
```jsx | ||
import { useCanWrite, useResource, useString } from '@tomic/react'; | ||
|
||
const ResourceDescription = () => { | ||
const resource = useResource('https://my-server.com/my-resource'); | ||
const [description, setDescription] = useString(resource, core.properties.description); | ||
const [canWrite] = useCanWrite(resource); | ||
|
||
if (canWrite) { | ||
return ( | ||
<textarea onChange={e => setDescription(e.target.value)}>{description}</textarea> | ||
<button onClick={() => resource.save()}>Save</button> | ||
) | ||
} | ||
|
||
return <p>{description}</p>; | ||
}; | ||
``` | ||
|
||
## Reference | ||
|
||
### Parameters | ||
|
||
- `resource: Resource` - The resource to check write access for. | ||
- `agent?: Agent` - Optional different agent to check write access for. Defaults to the current agent. | ||
|
||
### Returns | ||
|
||
Returns a tuple with the following fields: | ||
|
||
- `canWrite: boolean` - Whether the agent can write to the resource. | ||
- `msg: string` - An error message if the agent cannot write to the resource. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# useCollection | ||
|
||
The useCollection hook is used to fetch a [Collection](../js-lib/collection.md). | ||
It returns the collection together with a function to invalidate and re-fetch it. | ||
|
||
```typescript | ||
// Create a collection of all agents on the drive. | ||
const { collection ,invalidateCollection } = useCollection({ | ||
property: core.properties.isA, | ||
value: core.classes.Agent | ||
}); | ||
``` | ||
|
||
## Reference | ||
|
||
### Parameters | ||
|
||
- **query**: QueryFilter - The query used to build the collection | ||
- **pageSize**: number - The max number of items per page | ||
|
||
### Returns | ||
|
||
Returns an object containing the following items: | ||
|
||
- **collection**: [Collection](../js-lib/collection.md) - The collection. | ||
- **invalidateCollection**: `function` - A function to invalidate and re-fetch the collection. | ||
|
||
## QueryFilter | ||
|
||
A QueryFilter is an object with the following properties: | ||
|
||
| Name | Type | Description | | ||
| --- | --- | --- | | ||
| property | `string` | The subject of the property you want to filter by. | | ||
| value | `string` | The value of the property you want to filter by. | | ||
| sort_by | `string` | The subject of the property you want to sort by. By default collections are sorted by subject | | ||
| sort_desc | `boolean` | If true, the collection will be sorted in descending order. (Default: false) | | ||
|
||
## Additional Hooks | ||
|
||
Working with collections in React can be a bit tedious because most methods of `Collection` are asynchronous. | ||
Luckily, we made some extra hooks to help with the most common patterns. | ||
|
||
### useCollectionPage | ||
|
||
The `useCollectionPage` hook makes it easy to create paginated views. It takes a collection and a page number and returns the items on that page. | ||
|
||
```jsx | ||
import { | ||
core, | ||
useCollection, | ||
useCollectionPage, | ||
useResource, | ||
} from '@tomic/react'; | ||
import { useState } from 'react'; | ||
|
||
interface PaginatedChildrenProps { | ||
subject: string; | ||
} | ||
|
||
export const PaginatedChildren = ({ subject }: PaginatedChildrenProps) => { | ||
const [currentPage, setCurrentPage] = useState(0); | ||
|
||
const { collection } = useCollection({ | ||
property: core.properties.parent, | ||
value: subject, | ||
}); | ||
|
||
const items = useCollectionPage(collection, currentPage); | ||
|
||
return ( | ||
<div> | ||
<button onClick={() => setCurrentPage(p => Math.max(0, p - 1))}> | ||
Prev | ||
</button> | ||
<button | ||
onClick={() => | ||
setCurrentPage(p => Math.min(p + 1, collection.totalPages - 1)) | ||
} | ||
> | ||
Next | ||
</button> | ||
{items.map(item => ( | ||
<Item key={item} subject={item} /> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
const Item = ({ subject }: { subject: string }) => { | ||
const resource = useResource(subject); | ||
|
||
return <div>{resource.title}</div>; | ||
}; | ||
``` | ||
|
||
### UseMemberOfCollection | ||
|
||
Building virtualized lists is always difficult when working with unfamiliar data structures, especially when the data is paginated. | ||
The `UseMemberOfCollection` hook makes it easy. | ||
|
||
It takes a collection and index and returns the resource at that index. | ||
|
||
In this example, we use the [`react-window`](https://github.com/bvaughn/react-window?tab=readme-ov-file) library to render a virtualized list of comments. | ||
|
||
```jsx | ||
import { useCallback } from 'react'; | ||
import { FixedSizeList } from 'react-window'; | ||
import Autosizer from 'react-virtualized-auto-sizer'; | ||
import { useCollection, useMemberOfCollection } from '@tomic/react'; | ||
import { myOntology, type Comment } from './ontologies/myOntology'; | ||
|
||
const ListView = () => { | ||
// We create a collection of all comments. | ||
const { collection } = useCollection({ | ||
property: core.properties.isA, | ||
value: myOntology.classes.comment, | ||
}); | ||
|
||
// We have to define the CommentComponent inside the ListView component because it needs access to the collection. | ||
// Normally you'd pass it as a prop but that is not possible due to how react-window works. | ||
const CommentComp = useCallback(({index}: {index: number}) => { | ||
// Get the resource at the specified index. | ||
const comment = useMemberOfCollection<Comment>(collection, index); | ||
|
||
return ( | ||
<div> | ||
<UserInline subject={comment.props.writtenBy}> | ||
<p>{comment.props.description}</p> | ||
</div> | ||
); | ||
}, [collection]); | ||
|
||
return ( | ||
<Autosizer> | ||
{({width, height}) => ( | ||
<FixedSizeList | ||
height={height} | ||
itemCount={collection.totalMembers} | ||
itemSize={50} | ||
width={width} | ||
> | ||
{CommentComp} | ||
</FixedSizeList> | ||
)} | ||
</Autosizer> | ||
); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# useCurrentAgent | ||
|
||
`useCurrentAgent` is a convenient hook that returns the current agent set in the store. | ||
It also allows you to change the agent. | ||
|
||
It also updates whenever the agent changes. | ||
|
||
```ts | ||
const [agent, setAgent] = useCurrentAgent(); | ||
``` | ||
|
||
## Reference | ||
|
||
### Parameters | ||
|
||
none | ||
|
||
### Returns | ||
|
||
Returns a tuple with the following fields: | ||
|
||
- `agent: Agent` - The current agent set on the store. | ||
- `setAgent: (agent: Agent) => void` - A function to set the current agent on the store. |
Oops, something went wrong.