-
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 documentation for @tomic/react
- Loading branch information
Showing
6 changed files
with
374 additions
and
7 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
Empty file.
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,121 @@ | ||
# useResource | ||
|
||
`useResource` is the primary way to fetch data with Atomic React. | ||
It returns a [Resource](../js-lib/resource.md) object, that is still loading when initially returned. | ||
When the data is loaded, the component will re-render with the new data allowing you to show some UI before the content is available, resulting in a more responsive user experience. | ||
|
||
The hook also subscribes to changes meaning that the component will update whenever the data changes clientside and **even serverside**. | ||
You essentially get real-time features for free! | ||
|
||
```jsx | ||
import { useResource } from '@tomic/react'; | ||
|
||
export const Component = () => { | ||
const resource = useResource('https://my-atomic-server/my-resource'); | ||
|
||
// Optionally show a loading state | ||
if (resource.loading) { | ||
return <Loader /> | ||
} | ||
|
||
return ( | ||
<p>{resource.title}</p> | ||
) | ||
} | ||
``` | ||
|
||
## Typescript | ||
|
||
Just like the [`store.getResource`](../js-lib/resource.md#typescript) method, `useResource` can be annotated with a subject of a certain class. | ||
|
||
```typescript | ||
import { useResource } from '@tomic/react'; | ||
import type { Author } from './ontologies/blogsite' // <-- Generated with @tomic/cli | ||
// ... | ||
const resource = useResource<Author>('https://my-atomic-server/moderndayshakespear69') | ||
const age = Date.now() - resource.props.yearOfBirth | ||
``` | ||
|
||
## Reference | ||
|
||
### Parameters | ||
|
||
- **subject**: `string` - The subject of the resource you want to fetch. | ||
- **options**: `FetchOpts` - (Optional) Options for how the store should fetch the resource. | ||
|
||
**FetchOpts**: | ||
|
||
| Name | Type | Description | | ||
| --- | --- | --- | | ||
| allowIncomplete | `boolean` | ? | | ||
| noWebSocket | `boolean` | (Optional) If true, uses HTTP to fetch resources instead of websockets | | ||
| newResource | `Resource` | (Optional) If true, will not send a request to a server, it will simply create a new local resource.| | ||
|
||
### Returns | ||
|
||
[Resource](../js-lib/resource.md) - The fetched resource (might still be in a loading state). | ||
|
||
## Views | ||
|
||
A common way to build interfaces with Atomic React is to make a *view* component. | ||
Views are a concept where the component is responsible for rendering a resource in a certain way to fit in the context of the view type. | ||
|
||
The view selects a component based on the resource's class or renders a default view if there is no component for that class. | ||
In this example, we have a `ResourceInline` view that renders a resource inline in some text. | ||
For most resources, it will just render the name but for a Person or Product, it will render a special component. | ||
|
||
```jsx | ||
// views/inline/ResourceInline.tsx | ||
|
||
import { useResource } from '@tomic/react'; | ||
import { shop } from '../../ontologies/shop'; // <-- Generated with @tomic/cli | ||
import { PersonInline } from './PersonInline'; | ||
import { ProductInline } from './ProductInline'; | ||
|
||
interface ResourceInlineProps { | ||
subject: string; | ||
} | ||
|
||
export interface ResourceInlineViewProps<T> { | ||
resource: Resource<T>; | ||
} | ||
|
||
export const ResourceInline = ({ subject }: ResourceInlineProps): JSX.Element => { | ||
const resource = useResource(subject); | ||
|
||
const Comp = resource.matchClass({ | ||
[shop.classes.product]: ProductInline, | ||
[shop.classes.person]: PersonInline, | ||
}, Default); | ||
|
||
return <Comp subject={subject} /> | ||
} | ||
|
||
const Default = ({ subject }: ResourceInlineViewProps<unknown>) => { | ||
const resource = useResource(subject); | ||
|
||
return <span>{resource.title}</span> | ||
} | ||
``` | ||
|
||
The `PersonInline` view will render a person resource inline. | ||
It could render a mention-like thing with the person's name, their profile picture and a link to their profile for example. | ||
|
||
```jsx | ||
// views/inline/PersonInline.tsx | ||
import { useResource, Resource, type Server } from '@tomic/react'; | ||
import type { Person } from '../../ontologies/social' // <-- Generated with @tomic/cli | ||
import type { ResourceInlineViewProps } from './ResourceInline'; | ||
|
||
export const PersonInline = ({ resource }: ResourceInlineViewProps<Person>) => { | ||
const image = useResource<Server.File>(resource.props.image); | ||
|
||
return ( | ||
<span className="person-inline"> | ||
<img src={image.props.downloadUrl} className="profile-image-inline" /> | ||
<span>{resource.title}</span> | ||
</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,47 @@ | ||
# useStore | ||
|
||
You can use `useStore` when you need direct access to the store in your components. | ||
|
||
For example, on a login screen, you might want to set the agent on the store by calling `store.setAgent` after the user has entered their agent secret. | ||
|
||
```jsx | ||
import { Agent, useStore } from '@tomic/react'; | ||
|
||
export const Login = () => { | ||
const store = useStore(); | ||
const [agentSecret, setAgentSecret] = useState(''); | ||
|
||
const login = () => { | ||
try { | ||
const newAgent = Agent.fromSecret(agentSecret); | ||
store.setAgent(newAgent); | ||
} catch(e) { | ||
console.error(e); | ||
// Show error. | ||
} | ||
}; | ||
|
||
return ( | ||
<label> | ||
Agent Secret | ||
<input | ||
type="password" | ||
placeholder="My agent secret" | ||
value={agentSecret} | ||
onChange={e => setAgentSecret(e.target.value)} | ||
/> | ||
</label> | ||
<button onClick={login}>Login</button> | ||
); | ||
}; | ||
``` | ||
|
||
## Reference | ||
|
||
### Paramaters | ||
|
||
None. | ||
|
||
### Returns | ||
|
||
[Store](../js-lib/store.md) - The store object. |
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,147 @@ | ||
# useValue | ||
|
||
The `useValue` hook is used to read and write values from a resource. | ||
It looks and functions a lot like React's useState hook. | ||
|
||
```jsx | ||
import { useValue } from '@tomic/react'; | ||
|
||
const MyComponent = ({ subject }) => { | ||
const resource = useResource(subject); | ||
const [value, setValue] = useValue(resource, 'https://example.com/property'); | ||
|
||
return ( | ||
<div> | ||
<input value={value} onChange={e => setValue(e.target.value)} /> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
The `useValue` hook does not save the resource by default when the value is changed. | ||
This can be configured by passing an options object as the third argument with `commit` set to true. | ||
|
||
In practice, you will use typed versions of `useValue` more often. | ||
These offer better typescript typing and validation on writes. | ||
|
||
The following value hooks are available: | ||
|
||
- `useString` for string, slug and markdown values. | ||
- `useNumber` for float and integer values. | ||
- `useBoolean` for boolean values. | ||
- `useDate` for date and timestamp values. | ||
- `useArray` for ResourceArray values. | ||
|
||
## Reference | ||
|
||
### Parameters | ||
|
||
- **resource**: `Resource` - The resource object to read and write from. | ||
- **property**: `string` - The subject of the property you want to read and write. | ||
- **options**: `object` - (Optional) Options for how the value should be read and written. | ||
|
||
**Options**: | ||
| Name | Type | Description | | ||
| --- | --- | --- | | ||
| commit | `boolean` | If true, the resource will be saved when the value is changed. Default: `false` | | ||
| validate | `boolean` | If true, the value will be validated against the properties datatype. Default: `true`| | ||
| commitDebounce | `number` | The number of milliseconds to wait before saving the resource. Default: `100`| | ||
| handleValidationError | `function` | A function that is called when the value is invalid. | | ||
|
||
### Returns | ||
|
||
Returns an array (tuple) with two items: | ||
- **value**: type depends on the hook used - The value of the property. | ||
- **setValue**: `function` - A function to set the value of the property. | ||
|
||
## Some 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> | ||
); | ||
}; | ||
``` |
Oops, something went wrong.