# ShareDB Hooks

StartupJS имеет в себе удобные инструменты для работы с базой данных. Вы можете воспольоваться кастомными хуками для работы с ShareDB

## Что они делают

1. Обеспечивают реактивное взаимодействие React-приложений с ShareDB
1. Используют [Racer](https://derbyjs.com/docs/derby-0.10/models) для добавления модели в ваше приложение для выполнения любых манипуляций с данными
1. Модель работает как глобальный синглтон стейт, поэтому вы можете использовать её как замену для других стейт-менеджеров, таких как Redux или Mobx
1. Делают рендер компонента реактивным, как это делает в Mobx, когда изменяются какие-либо данные модели, которые вы использовали в рендере

## useDoc(collection, docId)

Позволяет подписаться на определенный Mongo-документ по его `id`

**Аргументы**

`collection (String)`: имя коллекции (обязательный)

`docId (String)`: идентификатор документа (обязательный)

**Возвращает**

`[doc, $doc]`, где:

  * `doc (Object)`: документ

  * `$doc (Model)`: модель, которая указывает на конкретный документ по пути `collection.docId`

**Пример**

```jsx
import React from 'react'
import { observer, useDoc } from 'startupjs'
import { Card, Span, Button } from '@startupjs/ui'

export default observer(function TodoCard ({
  todoId = 'DUMMY_ID'
}) {
  let [todo, $todo] = useDoc('todos', todoId)

  return (
    <Card>
      <Span>{todo.title}</Span>
      <Button
        size='s'
        onPress={() => $todo.del()}
      >
        Удалить
      </Button>
    </Card>
  )
```

**ВАЖНО**: идентификатор документа в Mongo хранится внутри поля `_id`. Но когда он попадает в модель, он заменяется полем `id`, и наоборот

## useQuery(collection, query)

Позволяет подписаться по Mongo запросу

**Аргументы**

`collection (String)`: имя коллекции (обязательный)

`query (Object)`: запрос (поддерживаются регулярные выражение, `$count` и `$aggregate`) (oбязательный)

**Возвращает**

`[docs, $docs]`, где:

  * `docs (Array)`: массив документов

  * `$docs (Model)`: модель, которая указывает на `collection`

**Пример**

```jsx
let [todos, $todos] = useQuery('todos', { completed: false })
// Мы получим все невыполненные задачи
```

**ВАЖНО**: модель `$docs`, которая возвращается из хука, указывает на глобальный путь коллекции. Вы можете использовать её чтобы легко получить доступ к документу с определенным `id`:

```jsx
let [todos, $todos] = useQuery('todos', { completed: false })

for (let todo of todos) {
  $todos.at(todo.id).setEach({
    completed: true,
    updatedAt: Date.now()
  })
}
```

## useQueryIds(collection, ids, options)

Позволяет подписаться на документы в коллекции по их `id`

**Аргументы**

`collection (String)`: имя коллекции (обязательный)

`ids (Array)`: массив из строк `id`

`options (Object)`:
  * `reverse (Boolean)`: меняет порядок документов в результирующем массиве

**Возвращает**

`[docs, $docs]`, где:

  * `docs (Array)`: массив документов

  * `$docs (Model)`: модель, которая указывает на `collection`

**Пример**

```jsx
export default observer(function TodosList ({ todoIds }) {
  let [todos, $todos] = useQueryIds('todos', todoIds)

  return (
    <Div>{todos.map(i => i.title).join(' ,')}</Div>
  )
})
```

## useQueryDoc(collection, query)

Позволяет подписаться на документ с помощью запроса. Этот хук похож на `useDoc()`, но он принимает `query` объект, вместо определенного `id`.
`$limit: 1` и `$sort: { createdAt: -1 }` добавляются в `query` автоматически (если не были заданы)

**Аргументы**

`collection (String)`: имя коллекции (обязательный)

`query (Object)`: объект запроса, как в `useQuery()`

**Пример**

```jsx
export default observer(function LatestTodo ({ userId }) {
  // { $sort: { createdAt: -1 }, $limit: 1 }
  // добавляется автоматически в запрос, таким образом будет возвращен самый новый игрок
  // Оно работает реактивно, поэтому всякий раз, когда пользователь создаст новую задачу, вы получаете новые данные и модель
  let [latestTodo, $latestTodo] = useQueryDoc('todos', { userId })
  if (!latestTodo) return null // <Loading />

  return (
    <div>Your last added todo: {latestTodo.title}</div>
  )
})
```

## useLocal(path)

Позволяет подписаться на данные по определенному пути, которые у вас уже есть в вашей локальной модели.

Чаще всего вы будете использовать этот хук для подписки на приватные коллекции, такие как `_page` или `_session`. Это очень полезно, когда вы хотите разделить состояние между несколькими компонентами.

Также можно подписаться на путь из общедоступной коллекции, например, если вы хотите работать с некоторым вложенным значением определенного документа, на который уже подписывались.

**Аргументы**

`path (String)`: путь к документу (обязательный)

**Возвращает**

`[value, $value]`, где:

  * `value (any)`: данные, расположенные по пути `path`

  * `$value (Model)`: модель, указывающая на путь `path`

**Пример**

```jsx
const SIDEBAR_OPENED = '_page.Sidebar.opened'

const Topbar = observer(() => {
  let [sidebarOpened, $sidebarOpened] = useLocal(SIDEBAR_OPENED)
  return <>
    <button
      onClick={() => $sidebarOpened.set(!sidebarOpened)}
    >Toggle Sidebar</button>
  </>
})

const Sidebar = observer(() => {
  let [sidebarOpened] = useLocal(SIDEBAR_OPENED)
  return sidebarOpened ? <p>Sidebar</p> : null
})

const App = observer(() => {
  return <>
    <Topbar />
    <Sidebar />
  </>
})
```

## useSession(path)

Удобный способ доступа к локальной коллекции `_session`

```jsx
let [userId, $userId] = useSession('userId')
// Это тоже самое что:
let [userId, $userId] = useLocal('_session.userId')
```

## usePage(path)

Удобный способ доступа к локальной коллекции `_page`

```jsx
let [game, $game] = usePage('game')
// Это тоже самое что:
let [game, $game] = useLocal('_page.game')
```

## useValue(defaultValue)

`Observable` альтернатива для `useState`

```jsx
const [inputValue, $inputValue] = useValue('')

<TextInput
  value={inputValue}
  onChangeText={value => $inputValue.set(value)}
/>
```

## useModel(path)

Возвращает модель по пути `path`. Если `path` не указан, то вернет модель с привязской к корневому пути.

```jsx
import React from 'react'
import {render} from 'react-dom'
import {observer, useModel, useLocal} from 'startupjs'

const Main = observer(() => {
  return (
    <div style={{display: 'flex'}}>
      <Content />
      <Sidebar />
    </div>
  )
})

const Content = observer(() => {
  let $showSidebar = useModel('_page.Sidebar.show')

  // sidebar will be opened without triggering rerendering of the <Content /> component (this component)
  return (
    <div>
      <p>I am Content</p>
      <button onClick={() => $showSidebar.setDiff(true)}>Open Sidebar</button>
    </div>
  )
})

const Sidebar = observer(() => {
  let [show, $show] = useLocal('_page.Sidebar.show')
  if (!show) return null
  return (
    <div>
      <p>I am Sidebar</p>
      <button onClick={() => $show.del()}>Close</button>
    </div>
  )
})

render(<Main />, document.body.appendChild(document.createElement('div')))
```
